From d99641be401c73eabee379ce8fdf9e57dd85114a Mon Sep 17 00:00:00 2001
From: Seth Hillbrand <seth@kipro-pcb.com>
Date: Thu, 14 Sep 2023 14:39:42 -0700
Subject: [PATCH] ADDED: Git integration support

Adds support for project-based git integration, branch support, commit,
revert and updates

Fixes https://gitlab.com/kicad/code/kicad/issues/10441
---
 CMakeLists.txt                                |   12 +
 cmake/Findlibgit2.cmake                       |   38 +
 common/CMakeLists.txt                         |   29 +
 common/advanced_config.cpp                    |    6 +
 common/bitmap_info.cpp                        |   14 +
 common/dialogs/git/dialog_git_auth.cpp        |   96 ++
 common/dialogs/git/dialog_git_auth.h          |   59 +
 common/dialogs/git/dialog_git_commit.cpp      |  204 +++
 common/dialogs/git/dialog_git_commit.h        |   62 +
 common/dialogs/git/dialog_git_progress.cpp    |   49 +
 common/dialogs/git/dialog_git_repository.cpp  |  448 ++++++
 common/dialogs/git/dialog_git_repository.h    |  124 ++
 .../git/dialog_git_repository_base.cpp        |  172 +++
 .../git/dialog_git_repository_base.fbp        | 1335 +++++++++++++++++
 .../dialogs/git/dialog_git_repository_base.h  |   82 +
 common/dialogs/git/dialog_git_switch.cpp      |  288 ++++
 common/dialogs/git/dialog_git_switch.h        |   79 +
 common/dialogs/git/panel_git_repos.cpp        |  377 +++++
 common/dialogs/git/panel_git_repos.h          |   59 +
 common/dialogs/git/panel_git_repos_base.cpp   |  167 +++
 common/dialogs/git/panel_git_repos_base.h     |   73 +
 .../git/panel_git_repositories_base.fbp       |  953 ++++++++++++
 common/eda_base_frame.cpp                     |   11 +
 common/eda_shape.cpp                          |  123 ++
 common/eda_text.cpp                           |   61 +
 common/gestfich.cpp                           |   50 +
 common/git/git_add_to_index_handler.cpp       |  108 ++
 common/git/git_add_to_index_handler.h         |   50 +
 common/git/git_clone_handler.cpp              |   82 +
 common/git/git_clone_handler.h                |   56 +
 common/git/git_commit_handler.cpp             |   49 +
 common/git/git_commit_handler.h               |   58 +
 common/git/git_compare_handler.cpp            |    0
 common/git/git_compare_handler.h              |    0
 common/git/git_progress.h                     |   72 +
 common/git/git_pull_handler.cpp               |  343 +++++
 common/git/git_pull_handler.h                 |  100 ++
 common/git/git_push_handler.cpp               |   79 +
 common/git/git_push_handler.h                 |   57 +
 common/git/git_remove_from_index_handler.cpp  |   98 ++
 common/git/git_remove_from_index_handler.h    |   50 +
 common/git/git_remove_vcs_handler.cpp         |    0
 common/git/git_remove_vcs_handler.h           |    0
 common/git/git_resolve_conflict_handler.cpp   |   39 +
 common/git/git_resolve_conflict_handler.h     |   43 +
 common/git/git_revert_handler.cpp             |  108 ++
 common/git/git_revert_handler.h               |   54 +
 common/git/git_switch_branch_handler.cpp      |    0
 common/git/git_switch_branch_handler.h        |    0
 common/git/git_sync_handler.cpp               |   44 +
 common/git/git_sync_handler.h                 |   43 +
 common/git/kicad_git_blob_reader.h            |   97 ++
 common/git/kicad_git_common.cpp               |  541 +++++++
 common/git/kicad_git_common.h                 |  145 ++
 common/git/kicad_git_errors.cpp               |   66 +
 common/git/kicad_git_errors.h                 |   81 +
 common/project/project_local_settings.cpp     |   12 +
 common/settings/common_settings.cpp           |   55 +
 common/single_top.cpp                         |    7 +
 eeschema/lib_field.cpp                        |   52 +
 eeschema/lib_field.h                          |    4 +
 eeschema/lib_item.cpp                         |    2 -
 eeschema/lib_item.h                           |   31 +-
 eeschema/lib_pin.cpp                          |  117 ++
 eeschema/lib_pin.h                            |   11 +-
 eeschema/lib_shape.cpp                        |   27 +
 eeschema/lib_shape.h                          |    4 +
 eeschema/lib_symbol.cpp                       |  136 ++
 eeschema/lib_symbol.h                         |   13 +-
 eeschema/lib_text.cpp                         |   27 +
 eeschema/lib_text.h                           |    4 +
 eeschema/lib_textbox.cpp                      |   29 +
 eeschema/lib_textbox.h                        |    4 +
 eeschema/sch_bitmap.cpp                       |   42 +
 eeschema/sch_bitmap.h                         |    4 +
 eeschema/sch_bus_entry.cpp                    |   39 +
 eeschema/sch_bus_entry.h                      |    4 +
 eeschema/sch_field.cpp                        |   68 +
 eeschema/sch_field.h                          |    4 +
 eeschema/sch_item.h                           |    9 +
 eeschema/sch_junction.cpp                     |   45 +
 eeschema/sch_junction.h                       |    4 +
 eeschema/sch_label.cpp                        |   63 +
 eeschema/sch_label.h                          |    4 +
 eeschema/sch_line.cpp                         |   63 +
 eeschema/sch_line.h                           |    4 +
 eeschema/sch_marker.h                         |   10 +
 eeschema/sch_no_connect.cpp                   |   31 +
 eeschema/sch_no_connect.h                     |    4 +
 eeschema/sch_pin.cpp                          |   37 +
 eeschema/sch_pin.h                            |    4 +
 eeschema/sch_shape.cpp                        |   24 +
 eeschema/sch_shape.h                          |    4 +
 eeschema/sch_sheet.cpp                        |   49 +
 eeschema/sch_sheet.h                          |    4 +
 eeschema/sch_sheet_path.cpp                   |   10 +
 eeschema/sch_sheet_pin.cpp                    |   33 +
 eeschema/sch_sheet_pin.h                      |    4 +
 eeschema/sch_symbol.cpp                       |   55 +
 eeschema/sch_symbol.h                         |    4 +
 eeschema/sch_text.cpp                         |   37 +
 eeschema/sch_text.h                           |    4 +
 eeschema/sch_textbox.cpp                      |   33 +
 eeschema/sch_textbox.h                        |    4 +
 eeschema/symbol_diff_frame.cpp                |  211 +++
 eeschema/symbol_diff_frame.h                  |   97 ++
 include/advanced_config.h                     |    5 +
 include/bitmaps/bitmaps_list.h                |    7 +
 include/board_item.h                          |   19 +
 include/eda_item.h                            |   18 +
 include/eda_shape.h                           |    4 +
 include/eda_text.h                            |   10 +
 include/frame_type.h                          |    4 +
 include/gestfich.h                            |    7 +
 include/pcb_group.h                           |    4 +
 include/project/project_local_settings.h      |    6 +
 include/scoped_set_reset.h                    |   28 +
 include/settings/common_settings.h            |   21 +
 kicad/kicad.cpp                               |    5 +
 kicad/kicad_id.h                              |   21 +
 kicad/menubar.cpp                             |    1 +
 kicad/project_tree.cpp                        |   25 +
 kicad/project_tree.h                          |    9 +
 kicad/project_tree_pane.cpp                   |  978 +++++++++++-
 kicad/project_tree_pane.h                     |   89 ++
 kicad/tools/kicad_manager_actions.cpp         |    8 +
 kicad/tools/kicad_manager_actions.h           |    1 +
 kicad/tools/kicad_manager_control.cpp         |   89 +-
 kicad/tools/kicad_manager_control.h           |    4 +
 libs/core/include/core/kicad_algo.h           |   31 +
 libs/kiplatform/CMakeLists.txt                |    8 +
 libs/kiplatform/gtk/secrets.cpp               |   84 ++
 libs/kiplatform/include/kiplatform/secrets.h  |   37 +
 libs/kiplatform/msw/secrets.cpp               |   60 +
 libs/kiplatform/osx/secrets.mm                |   75 +
 pcbnew/CMakeLists.txt                         |    6 +
 pcbnew/board.cpp                              |   90 +-
 pcbnew/board.h                                |   16 +-
 pcbnew/footprint.cpp                          |   71 +
 pcbnew/footprint.h                            |    4 +
 pcbnew/git/kigit_pcb_merge.cpp                |  180 +++
 pcbnew/git/kigit_pcb_merge.h                  |   85 ++
 pcbnew/netinfo.h                              |   10 +
 pcbnew/pad.cpp                                |  180 +++
 pcbnew/pad.h                                  |    4 +
 pcbnew/pcb_bitmap.cpp                         |   66 +
 pcbnew/pcb_bitmap.h                           |    4 +
 pcbnew/pcb_dimension.cpp                      |   95 ++
 pcbnew/pcb_dimension.h                        |    4 +
 pcbnew/pcb_field.cpp                          |   37 +-
 pcbnew/pcb_field.h                            |    4 +
 pcbnew/pcb_group.cpp                          |   47 +
 pcbnew/pcb_marker.h                           |   10 +
 pcbnew/pcb_shape.cpp                          |   62 +
 pcbnew/pcb_shape.h                            |    4 +
 pcbnew/pcb_target.cpp                         |   39 +
 pcbnew/pcb_target.h                           |    4 +
 pcbnew/pcb_text.cpp                           |   22 +
 pcbnew/pcb_text.h                             |    4 +
 pcbnew/pcb_textbox.cpp                        |   30 +
 pcbnew/pcb_textbox.h                          |    4 +
 pcbnew/pcb_track.cpp                          |  136 ++
 pcbnew/pcb_track.h                            |   12 +
 .../scripting/pcbnew_action_plugins.cpp       |    4 +-
 pcbnew/teardrop/teardrop_parameters.h         |   18 +
 pcbnew/zone.cpp                               |  134 ++
 pcbnew/zone.h                                 |    4 +
 resources/bitmaps_png/CMakeLists.txt          |    7 +
 resources/bitmaps_png/png/git_add_16.png      |  Bin 0 -> 120 bytes
 resources/bitmaps_png/png/git_add_dark_16.png |  Bin 0 -> 116 bytes
 .../bitmaps_png/png/git_changed_ahead_16.png  |  Bin 0 -> 412 bytes
 .../png/git_changed_ahead_dark_16.png         |  Bin 0 -> 417 bytes
 resources/bitmaps_png/png/git_conflict_16.png |  Bin 0 -> 303 bytes
 .../bitmaps_png/png/git_conflict_dark_16.png  |  Bin 0 -> 312 bytes
 resources/bitmaps_png/png/git_delete_16.png   |  Bin 0 -> 407 bytes
 .../bitmaps_png/png/git_delete_dark_16.png    |  Bin 0 -> 385 bytes
 .../bitmaps_png/png/git_good_check_16.png     |  Bin 0 -> 236 bytes
 .../png/git_good_check_dark_16.png            |  Bin 0 -> 236 bytes
 resources/bitmaps_png/png/git_modified_16.png |  Bin 0 -> 348 bytes
 .../bitmaps_png/png/git_modified_dark_16.png  |  Bin 0 -> 336 bytes
 .../bitmaps_png/png/git_out_of_date_16.png    |  Bin 0 -> 405 bytes
 .../png/git_out_of_date_dark_16.png           |  Bin 0 -> 405 bytes
 .../bitmaps_png/sources/dark/git_add.svg      |   82 +
 .../sources/dark/git_changed_ahead.svg        |   89 ++
 .../bitmaps_png/sources/dark/git_conflict.svg |   99 ++
 .../bitmaps_png/sources/dark/git_delete.svg   |   76 +
 .../sources/dark/git_good_check.svg           |   84 ++
 .../bitmaps_png/sources/dark/git_modified.svg |   77 +
 .../sources/dark/git_out_of_date.svg          |   87 ++
 .../bitmaps_png/sources/light/git_add.svg     |   82 +
 .../sources/light/git_changed_ahead.svg       |   89 ++
 .../sources/light/git_conflict.svg            |   99 ++
 .../bitmaps_png/sources/light/git_delete.svg  |   11 +
 .../sources/light/git_good_check.svg          |   84 ++
 .../sources/light/git_modified.svg            |   77 +
 .../sources/light/git_out_of_date.svg         |   87 ++
 196 files changed, 13121 insertions(+), 38 deletions(-)
 create mode 100644 cmake/Findlibgit2.cmake
 create mode 100644 common/dialogs/git/dialog_git_auth.cpp
 create mode 100644 common/dialogs/git/dialog_git_auth.h
 create mode 100644 common/dialogs/git/dialog_git_commit.cpp
 create mode 100644 common/dialogs/git/dialog_git_commit.h
 create mode 100644 common/dialogs/git/dialog_git_progress.cpp
 create mode 100644 common/dialogs/git/dialog_git_repository.cpp
 create mode 100644 common/dialogs/git/dialog_git_repository.h
 create mode 100644 common/dialogs/git/dialog_git_repository_base.cpp
 create mode 100644 common/dialogs/git/dialog_git_repository_base.fbp
 create mode 100644 common/dialogs/git/dialog_git_repository_base.h
 create mode 100644 common/dialogs/git/dialog_git_switch.cpp
 create mode 100644 common/dialogs/git/dialog_git_switch.h
 create mode 100644 common/dialogs/git/panel_git_repos.cpp
 create mode 100644 common/dialogs/git/panel_git_repos.h
 create mode 100644 common/dialogs/git/panel_git_repos_base.cpp
 create mode 100644 common/dialogs/git/panel_git_repos_base.h
 create mode 100644 common/dialogs/git/panel_git_repositories_base.fbp
 create mode 100644 common/git/git_add_to_index_handler.cpp
 create mode 100644 common/git/git_add_to_index_handler.h
 create mode 100644 common/git/git_clone_handler.cpp
 create mode 100644 common/git/git_clone_handler.h
 create mode 100644 common/git/git_commit_handler.cpp
 create mode 100644 common/git/git_commit_handler.h
 create mode 100644 common/git/git_compare_handler.cpp
 create mode 100644 common/git/git_compare_handler.h
 create mode 100644 common/git/git_progress.h
 create mode 100644 common/git/git_pull_handler.cpp
 create mode 100644 common/git/git_pull_handler.h
 create mode 100644 common/git/git_push_handler.cpp
 create mode 100644 common/git/git_push_handler.h
 create mode 100644 common/git/git_remove_from_index_handler.cpp
 create mode 100644 common/git/git_remove_from_index_handler.h
 create mode 100644 common/git/git_remove_vcs_handler.cpp
 create mode 100644 common/git/git_remove_vcs_handler.h
 create mode 100644 common/git/git_resolve_conflict_handler.cpp
 create mode 100644 common/git/git_resolve_conflict_handler.h
 create mode 100644 common/git/git_revert_handler.cpp
 create mode 100644 common/git/git_revert_handler.h
 create mode 100644 common/git/git_switch_branch_handler.cpp
 create mode 100644 common/git/git_switch_branch_handler.h
 create mode 100644 common/git/git_sync_handler.cpp
 create mode 100644 common/git/git_sync_handler.h
 create mode 100644 common/git/kicad_git_blob_reader.h
 create mode 100644 common/git/kicad_git_common.cpp
 create mode 100644 common/git/kicad_git_common.h
 create mode 100644 common/git/kicad_git_errors.cpp
 create mode 100644 common/git/kicad_git_errors.h
 create mode 100644 eeschema/symbol_diff_frame.cpp
 create mode 100644 eeschema/symbol_diff_frame.h
 create mode 100644 libs/kiplatform/gtk/secrets.cpp
 create mode 100644 libs/kiplatform/include/kiplatform/secrets.h
 create mode 100644 libs/kiplatform/msw/secrets.cpp
 create mode 100644 libs/kiplatform/osx/secrets.mm
 create mode 100644 pcbnew/git/kigit_pcb_merge.cpp
 create mode 100644 pcbnew/git/kigit_pcb_merge.h
 create mode 100644 resources/bitmaps_png/png/git_add_16.png
 create mode 100644 resources/bitmaps_png/png/git_add_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_changed_ahead_16.png
 create mode 100644 resources/bitmaps_png/png/git_changed_ahead_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_conflict_16.png
 create mode 100644 resources/bitmaps_png/png/git_conflict_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_delete_16.png
 create mode 100644 resources/bitmaps_png/png/git_delete_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_good_check_16.png
 create mode 100644 resources/bitmaps_png/png/git_good_check_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_modified_16.png
 create mode 100644 resources/bitmaps_png/png/git_modified_dark_16.png
 create mode 100644 resources/bitmaps_png/png/git_out_of_date_16.png
 create mode 100644 resources/bitmaps_png/png/git_out_of_date_dark_16.png
 create mode 100644 resources/bitmaps_png/sources/dark/git_add.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_changed_ahead.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_conflict.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_delete.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_good_check.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_modified.svg
 create mode 100644 resources/bitmaps_png/sources/dark/git_out_of_date.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_add.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_changed_ahead.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_conflict.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_delete.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_good_check.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_modified.svg
 create mode 100644 resources/bitmaps_png/sources/light/git_out_of_date.svg

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec62cbf753..ff0e180af9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -746,6 +746,12 @@ find_package(ZLIB REQUIRED)
 #
 find_package( CURL REQUIRED )
 
+#
+# Find libssl, required
+#
+find_package( OpenSSL REQUIRED )
+include_directories( SYSTEM ${OPENSSL_INCLUDE_DIR} )
+
 #
 # Find Cairo library, required
 #
@@ -755,6 +761,12 @@ include_directories( SYSTEM ${CAIRO_INCLUDE_DIR} )
 find_package( Pixman 0.30 REQUIRED )
 include_directories( SYSTEM ${PIXMAN_INCLUDE_DIR} )
 
+# Find libgit2, required
+find_package( libgit2 REQUIRED )
+
+# Set include directories for libgit
+include_directories(${LIBGIT2_INCLUDE_DIRS})
+
 #
 # Find Boost headers and libraries, required.
 set( BOOST_REQUESTED_COMPONENTS locale )    # locale is required by nanoodbc/database libraries
diff --git a/cmake/Findlibgit2.cmake b/cmake/Findlibgit2.cmake
new file mode 100644
index 0000000000..5a1b1ef2bc
--- /dev/null
+++ b/cmake/Findlibgit2.cmake
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: 2014 Dan Leinir Turthra Jensen <admin@leinir.dk
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+# - Try to find the libgit2 library
+# Once done this will define
+#
+#  LIBGIT2_FOUND - System has libgit2
+#  LIBGIT2_INCLUDE_DIR - The libgit2 include directory
+#  LIBGIT2_LIBRARIES - The libraries needed to use libgit2
+#  LIBGIT2_DEFINITIONS - Compiler switches required for using libgit2
+
+
+# use pkg-config to get the directories and then use these values
+# in the FIND_PATH() and FIND_LIBRARY() calls
+find_package(PkgConfig)
+pkg_search_module(PC_LIBGIT2 libgit2)
+
+set(LIBGIT2_DEFINITIONS ${PC_LIBGIT2_CFLAGS_OTHER})
+
+find_path(LIBGIT2_INCLUDE_DIR NAMES git2.h
+   HINTS
+   ${PC_LIBGIT2_INCLUDEDIR}
+   ${PC_LIBGIT2_INCLUDE_DIRS}
+)
+
+find_library(LIBGIT2_LIBRARIES NAMES git2
+   HINTS
+   ${PC_LIBGIT2_LIBDIR}
+   ${PC_LIBGIT2_LIBRARY_DIRS}
+)
+
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(libgit2 DEFAULT_MSG LIBGIT2_LIBRARIES LIBGIT2_INCLUDE_DIR)
+
+mark_as_advanced(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARIES)
\ No newline at end of file
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index be88a88abb..ba6c9d8700 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -131,6 +131,8 @@ target_link_libraries( kicommon
     fmt::fmt
     CURL::libcurl
     ${wxWidgets_LIBRARIES}
+    ${LIBGIT2_LIBRARIES}
+    ${OPENSSL_LIBRARIES}
 
     # needed by kiid to allow linking for Boost for the UUID against bcrypt (msys2 only)
     ${EXTRA_LIBS}
@@ -195,7 +197,19 @@ set( COMMON_ABOUT_DLG_SRCS
     dialog_about/dialog_about_base.cpp
     )
 
+set( COMMON_GIT_DLG_SRCS
+    dialogs/git/dialog_git_commit.cpp
+    dialogs/git/dialog_git_switch.cpp
+    dialogs/git/dialog_git_auth.cpp
+    dialogs/git/dialog_git_repository.cpp
+    dialogs/git/dialog_git_repository_base.cpp
+    dialogs/git/panel_git_repos.cpp
+    dialogs/git/panel_git_repos_base.cpp
+
+    )
+
 set( COMMON_DLG_SRCS
+    ${COMMON_GIT_DLG_SRCS}
     dialogs/dialog_assign_netclass.cpp
     dialogs/dialog_assign_netclass_base.cpp
     dialogs/dialog_book_reporter.cpp
@@ -390,6 +404,20 @@ set( COMMON_IMPORT_GFX_SRCS
     import_gfx/svg_import_plugin.cpp
     )
 
+set( COMMON_GIT_SRCS
+    git/git_add_to_index_handler.cpp
+    git/git_clone_handler.cpp
+    git/git_commit_handler.cpp
+    git/git_pull_handler.cpp
+    git/git_push_handler.cpp
+    git/git_remove_from_index_handler.cpp
+    git/git_resolve_conflict_handler.cpp
+    git/git_revert_handler.cpp
+    git/git_sync_handler.cpp
+    git/kicad_git_common.cpp
+    git/kicad_git_errors.cpp
+    )
+
 set( COMMON_SRCS
     ${LIB_KICAD_SRCS}
     ${COMMON_ABOUT_DLG_SRCS}
@@ -405,6 +433,7 @@ set( COMMON_SRCS
     ${PLUGINS_EASYEDAPRO_SRCS}
 	${FONT_SRCS}
     ${COMMON_IMPORT_GFX_SRCS}
+    ${COMMON_GIT_SRCS}
     jobs/job_dispatcher.cpp
     background_jobs_monitor.cpp
     base_screen.cpp
diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp
index 8e2f8afcc6..ba558f7f72 100644
--- a/common/advanced_config.cpp
+++ b/common/advanced_config.cpp
@@ -215,6 +215,8 @@ static const wxChar UseClipper2[] = wxT( "UseClipper2" );
 
 static const wxChar EnableGenerators[] = wxT( "EnableGenerators" );
 
+static const wxChar EnableGit[] = wxT( "EnableGit" );
+
 /**
  * The time in milliseconds to wait before displaying a disambiguation menu.
  */
@@ -345,6 +347,7 @@ ADVANCED_CFG::ADVANCED_CFG()
     m_ShowRepairSchematic       = false;
     m_ShowPropertiesPanel       = false;
     m_EnableGenerators          = false;
+    m_EnableGit                 = false;
 
     m_3DRT_BevelHeight_um       = 30;
     m_3DRT_BevelExtentFactor    = 1.0 / 16.0;
@@ -518,6 +521,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
     configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGenerators,
                                                 &m_EnableGenerators, m_EnableGenerators ) );
 
+    configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGit,
+                                                &m_EnableGit, m_EnableGit ) );
+
 
 
     // Special case for trace mask setting...we just grab them and set them immediately
diff --git a/common/bitmap_info.cpp b/common/bitmap_info.cpp
index 626e079477..e750ca8f6d 100644
--- a/common/bitmap_info.cpp
+++ b/common/bitmap_info.cpp
@@ -30,6 +30,13 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
     aBitmapInfoCache[BITMAPS::e_48].emplace_back( BITMAPS::e_48, wxT( "e_48_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::e_96].emplace_back( BITMAPS::e_96, wxT( "e_96_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::e_192].emplace_back( BITMAPS::e_192, wxT( "e_192_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_add].emplace_back( BITMAPS::git_add, wxT( "git_add_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_changed_ahead].emplace_back( BITMAPS::git_changed_ahead, wxT( "git_changed_ahead_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_conflict].emplace_back( BITMAPS::git_conflict, wxT( "git_conflict_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_delete].emplace_back( BITMAPS::git_delete, wxT( "git_delete_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_good_check].emplace_back( BITMAPS::git_good_check, wxT( "git_good_check_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_modified].emplace_back( BITMAPS::git_modified, wxT( "git_modified_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_out_of_date].emplace_back( BITMAPS::git_out_of_date, wxT( "git_out_of_date_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::icon_bitmap2component_16].emplace_back( BITMAPS::icon_bitmap2component_16, wxT( "icon_bitmap2component_16_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::icon_eeschema_16].emplace_back( BITMAPS::icon_eeschema_16, wxT( "icon_eeschema_16_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::icon_gerbview_16].emplace_back( BITMAPS::icon_gerbview_16, wxT( "icon_gerbview_16_16.png" ), 16, wxT( "light" ) );
@@ -101,6 +108,13 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
     aBitmapInfoCache[BITMAPS::visibility].emplace_back( BITMAPS::visibility, wxT( "visibility_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::visibility_off].emplace_back( BITMAPS::visibility_off, wxT( "visibility_off_16.png" ), 16, wxT( "light" ) );
     aBitmapInfoCache[BITMAPS::www].emplace_back( BITMAPS::www, wxT( "www_16.png" ), 16, wxT( "light" ) );
+    aBitmapInfoCache[BITMAPS::git_add].emplace_back( BITMAPS::git_add, wxT( "git_add_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_changed_ahead].emplace_back( BITMAPS::git_changed_ahead, wxT( "git_changed_ahead_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_conflict].emplace_back( BITMAPS::git_conflict, wxT( "git_conflict_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_delete].emplace_back( BITMAPS::git_delete, wxT( "git_delete_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_good_check].emplace_back( BITMAPS::git_good_check, wxT( "git_good_check_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_modified].emplace_back( BITMAPS::git_modified, wxT( "git_modified_dark_16.png" ), 16, wxT( "dark" ) );
+    aBitmapInfoCache[BITMAPS::git_out_of_date].emplace_back( BITMAPS::git_out_of_date, wxT( "git_out_of_date_dark_16.png" ), 16, wxT( "dark" ) );
     aBitmapInfoCache[BITMAPS::icon_bitmap2component_16].emplace_back( BITMAPS::icon_bitmap2component_16, wxT( "icon_bitmap2component_16_dark_16.png" ), 16, wxT( "dark" ) );
     aBitmapInfoCache[BITMAPS::icon_eeschema_16].emplace_back( BITMAPS::icon_eeschema_16, wxT( "icon_eeschema_16_dark_16.png" ), 16, wxT( "dark" ) );
     aBitmapInfoCache[BITMAPS::icon_gerbview_16].emplace_back( BITMAPS::icon_gerbview_16, wxT( "icon_gerbview_16_dark_16.png" ), 16, wxT( "dark" ) );
diff --git a/common/dialogs/git/dialog_git_auth.cpp b/common/dialogs/git/dialog_git_auth.cpp
new file mode 100644
index 0000000000..18fd2ed32f
--- /dev/null
+++ b/common/dialogs/git/dialog_git_auth.cpp
@@ -0,0 +1,96 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "dialog_git_auth.h"
+
+DIALOG_GIT_AUTH::DIALOG_GIT_AUTH(wxWindow* parent)
+    : DIALOG_SHIM(parent, wxID_ANY, _("Connection"), wxDefaultPosition, wxSize(400, 300))
+{
+    CreateControls();
+    Centre();
+}
+
+DIALOG_GIT_AUTH::~DIALOG_GIT_AUTH()
+{
+}
+
+void DIALOG_GIT_AUTH::CreateControls()
+{
+    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
+    this->SetSizer(mainSizer);
+
+    m_NameTextCtrl = new wxTextCtrl(this, wxID_ANY);
+    m_UrlTextCtrl = new wxTextCtrl(this, wxID_ANY);
+    m_AuthChoice = new wxChoice(this, wxID_ANY);
+    m_AuthChoice->Append(_("Basic"));
+    m_AuthChoice->Append(_("SSH"));
+    m_UserNameTextCtrl = new wxTextCtrl(this, wxID_ANY);
+    m_PasswordTextCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PASSWORD);
+    m_PublicKeyPicker = new wxFilePickerCtrl(this, wxID_ANY);
+    m_PublicKeyPicker->Hide();
+
+    m_TestButton = new wxButton(this, wxID_ANY, _("Test"));
+    m_OkButton = new wxButton(this, wxID_OK, _("OK"));
+    m_CancelButton = new wxButton(this, wxID_CANCEL, _("Cancel"));
+
+    mainSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), 0, wxALL, 10);
+    mainSizer->Add(m_NameTextCtrl, 0, wxEXPAND | wxALL, 10);
+    mainSizer->Add(new wxStaticText(this, wxID_ANY, _("Url")), 0, wxALL, 10);
+    mainSizer->Add(m_UrlTextCtrl, 0, wxEXPAND | wxALL, 10);
+    mainSizer->Add(new wxStaticText(this, wxID_ANY, _("Authentication")), 0, wxALL, 10);
+    mainSizer->Add(m_AuthChoice, 0, wxEXPAND | wxALL, 10);
+    mainSizer->Add(new wxStaticText(this, wxID_ANY, _("User Name")), 0, wxALL, 10);
+    mainSizer->Add(m_UserNameTextCtrl, 0, wxEXPAND | wxALL, 10);
+    mainSizer->Add(new wxStaticText(this, wxID_ANY, _("Password")), 0, wxALL, 10);
+    mainSizer->Add(m_PasswordTextCtrl, 0, wxEXPAND | wxALL, 10);
+    mainSizer->Add(m_PublicKeyPicker, 0, wxEXPAND | wxALL, 10);
+
+
+    wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
+    buttonSizer->Add(m_TestButton, 1, wxALL, 10);
+    buttonSizer->Add(m_OkButton, 1, wxALL, 10);
+    buttonSizer->Add(m_CancelButton, 1, wxALL, 10);
+    mainSizer->Add(buttonSizer, 0, wxALIGN_RIGHT);
+
+    mainSizer->Layout();
+
+    // Bind event for authentication method choice change
+    m_AuthChoice->Bind(wxEVT_CHOICE, &DIALOG_GIT_AUTH::onAuthChoiceChanged, this);
+    m_TestButton->Bind(wxEVT_BUTTON, &DIALOG_GIT_AUTH::onTestClick, this );
+}
+
+void DIALOG_GIT_AUTH::onAuthChoiceChanged(wxCommandEvent& event)
+{
+    if (m_AuthChoice->GetStringSelection() == "SSH")
+    {
+        m_PasswordTextCtrl->Hide();
+        m_PublicKeyPicker->Show();
+    }
+    else
+    {
+        m_PasswordTextCtrl->Show();
+        m_PublicKeyPicker->Hide();
+    }
+
+    Layout();  // Re-arrange the controls
+}
diff --git a/common/dialogs/git/dialog_git_auth.h b/common/dialogs/git/dialog_git_auth.h
new file mode 100644
index 0000000000..4016a43b7c
--- /dev/null
+++ b/common/dialogs/git/dialog_git_auth.h
@@ -0,0 +1,59 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_REPOSITORY_DIALOG_H
+#define GIT_REPOSITORY_DIALOG_H
+
+#include <wx/button.h>
+#include <wx/choice.h>
+#include <wx/filepicker.h>
+#include <wx/textctrl.h>
+#include <wx/wx.h>
+
+#include <dialog_shim.h>
+
+class DIALOG_GIT_AUTH : public DIALOG_SHIM
+{
+public:
+    DIALOG_GIT_AUTH( wxWindow* parent );
+    virtual ~DIALOG_GIT_AUTH();
+
+private:
+    void CreateControls();
+    void onAuthChoiceChanged( wxCommandEvent& event );
+    void onTestClick( wxCommandEvent& aEvent );
+    void onOKClick( wxCommandEvent& aEvent );
+    void onCancelClick( wxCommandEvent& aEvent );
+
+    wxTextCtrl*       m_NameTextCtrl;
+    wxTextCtrl*       m_UrlTextCtrl;
+    wxChoice*         m_AuthChoice;
+    wxTextCtrl*       m_UserNameTextCtrl;
+    wxTextCtrl*       m_PasswordTextCtrl;
+    wxButton*         m_TestButton;
+    wxButton*         m_OkButton;
+    wxButton*         m_CancelButton;
+    wxFilePickerCtrl* m_PublicKeyPicker;
+};
+
+#endif // GIT_REPOSITORY_DIALOG_H
diff --git a/common/dialogs/git/dialog_git_commit.cpp b/common/dialogs/git/dialog_git_commit.cpp
new file mode 100644
index 0000000000..d55144617c
--- /dev/null
+++ b/common/dialogs/git/dialog_git_commit.cpp
@@ -0,0 +1,204 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "dialog_git_commit.h"
+
+#include <bitmaps/bitmaps_list.h>
+#include <bitmaps/bitmap_types.h>
+#include <git/kicad_git_common.h>
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/listctrl.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
+DIALOG_GIT_COMMIT::DIALOG_GIT_COMMIT( wxWindow* parent, git_repository* repo,
+                                      const wxString&                defaultAuthorName,
+                                      const wxString&                defaultAuthorEmail,
+                                      const std::map<wxString, int>& filesToCommit ) :
+        DIALOG_SHIM( parent, wxID_ANY, "Commit Changes" )
+{
+    wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
+
+
+    // List Control for files to commit
+    m_listCtrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
+                                wxLC_REPORT | wxLC_SINGLE_SEL  );
+
+    // Set up columns
+    m_listCtrl->EnableCheckBoxes();
+    m_listCtrl->AppendColumn( "Filename" );
+    m_listCtrl->AppendColumn( "Placeholder" );
+
+    // Set column widths
+    m_listCtrl->SetColumnWidth(0, 200);
+    m_listCtrl->SetColumnWidth(1, 200);
+
+    // Set up image list for icons
+    wxImageList* imageList = new wxImageList( 16, 16, true,
+                                         static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_LAST ) );
+
+    imageList->Add( KiBitmap( BITMAPS::git_good_check ) );   // PLACEHOLDER
+    imageList->Add( KiBitmap( BITMAPS::git_good_check ) );   // GIT_STATUS_CURRENT
+    imageList->Add( KiBitmap( BITMAPS::git_modified ) );     // GIT_STATUS_MODIFIED
+    imageList->Add( KiBitmap( BITMAPS::git_add ) );          // GIT_STATUS_ADDED
+    imageList->Add( KiBitmap( BITMAPS::git_delete ) );       // GIT_STATUS_DELETED
+    imageList->Add( KiBitmap( BITMAPS::git_out_of_date ) );  // GIT_STATUS_BEHIND
+    imageList->Add( KiBitmap( BITMAPS::git_changed_ahead ) );// GIT_STATUS_AHEAD
+    imageList->Add( KiBitmap( BITMAPS::git_conflict ) );     // GIT_STATUS_CONFLICTED
+
+    // Assign the image list to the list control
+    m_listCtrl->SetImageList(imageList, wxIMAGE_LIST_SMALL);
+
+    // Populate list control with items
+    for ( auto& [filename, status] : filesToCommit )
+    {
+        int i = m_listCtrl->GetItemCount();
+        m_listCtrl->InsertItem(i, filename );
+
+        if( status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
+        {
+            m_listCtrl->SetItem( i, 1, "New" );
+            m_listCtrl->SetItemImage(i, static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED ) );
+        }
+        else if( status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
+        {
+            m_listCtrl->SetItem( i, 1, "Modified" );
+            m_listCtrl->SetItemImage(i, static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED ) );
+        }
+        else if( status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
+        {
+            m_listCtrl->SetItem( i, 1, "Deleted" );
+            m_listCtrl->SetItemImage(i, static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED ) );
+        }
+        else
+        {
+            printf(" Unknown status: %d\n", status );
+        }
+    }
+
+    sizer->Add(m_listCtrl, 1, wxEXPAND | wxALL, 5);
+
+    // Commit Message Text Control
+    wxStaticText* commitMessageLabel = new wxStaticText( this, wxID_ANY, _( "Commit Message:" ) );
+    m_commitMessageTextCtrl =
+            new wxTextCtrl( this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
+    sizer->Add( commitMessageLabel, 0, wxALL, 5 );
+    sizer->Add( m_commitMessageTextCtrl, 1, wxEXPAND | wxALL, 5 );
+
+    // Author Name and Email Text Control
+    wxStaticText* authorLabel = new wxStaticText( this, wxID_ANY, _( "Author:" ) );
+    wxString      defaultAuthor = defaultAuthorName + " <" + defaultAuthorEmail + ">";
+    m_authorTextCtrl =
+            new wxTextCtrl( this, wxID_ANY, defaultAuthor, wxDefaultPosition, wxDefaultSize, 0 );
+    sizer->Add( authorLabel, 0, wxALL, 5 );
+    sizer->Add( m_authorTextCtrl, 0, wxEXPAND | wxALL, 5 );
+
+    // OK and Cancel Buttons
+
+    wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
+
+    m_okButton = new wxButton( this, wxID_OK, _( "OK" ) );
+    wxButton*   cancelButton = new wxButton( this, wxID_CANCEL, _( "Cancel" ) );
+    buttonSizer->Add( cancelButton, 0, wxALL, 5 );
+    buttonSizer->Add( m_okButton, 0, wxALL, 5 );
+    buttonSizer->Realize();
+
+    sizer->Add( buttonSizer, 0, wxALIGN_RIGHT | wxALL, 5 );
+
+    SetSizerAndFit( sizer );
+
+    SetupStandardButtons( { { wxID_OK, _( "C&ommit" ) } } );
+
+    // Bind events
+    Bind( wxEVT_TEXT, &DIALOG_GIT_COMMIT::OnTextChanged, this, m_commitMessageTextCtrl->GetId() );
+
+    // Set the repository and defaults
+    m_repo = repo;
+    m_defaultAuthorName = defaultAuthorName;
+    m_defaultAuthorEmail = defaultAuthorEmail;
+}
+
+
+void DIALOG_GIT_COMMIT::OnTextChanged( wxCommandEvent& aEvent )
+{
+    if( m_commitMessageTextCtrl->GetValue().IsEmpty() )
+    {
+        m_okButton->Disable();
+        m_okButton->SetToolTip( _( "Commit message cannot be empty" ) );
+    }
+    else
+    {
+        m_okButton->Enable();
+        m_okButton->SetToolTip( wxEmptyString );
+    }
+}
+
+
+wxString DIALOG_GIT_COMMIT::GetCommitMessage() const
+{
+    return m_commitMessageTextCtrl->GetValue();
+}
+
+
+wxString DIALOG_GIT_COMMIT::GetAuthorName() const
+{
+    wxString authorText = m_authorTextCtrl->GetValue();
+    size_t   pos = authorText.find( '<' );
+
+    if( pos != wxString::npos )
+        return authorText.substr( 0, pos ).Trim();
+
+    return wxEmptyString;
+}
+
+
+wxString DIALOG_GIT_COMMIT::GetAuthorEmail() const
+{
+    wxString authorText = m_authorTextCtrl->GetValue();
+    size_t   startPos = authorText.find( '<' );
+    size_t   endPos = authorText.find( '>' );
+
+    if( startPos != wxString::npos && endPos != wxString::npos && startPos < endPos )
+        return authorText.substr( startPos + 1, endPos - startPos - 1 ).Trim();
+
+    return wxEmptyString;
+}
+
+
+std::vector<wxString> DIALOG_GIT_COMMIT::GetSelectedFiles() const
+{
+    std::vector<wxString> selectedFiles;
+
+    long item = -1;
+    while( ( item = m_listCtrl->GetNextItem( item, wxLIST_NEXT_ALL ) )
+           != -1 )
+    {
+        if( m_listCtrl->IsItemChecked( item ) )
+            selectedFiles.push_back( m_listCtrl->GetItemText( item ) );
+    }
+
+    return selectedFiles;
+}
\ No newline at end of file
diff --git a/common/dialogs/git/dialog_git_commit.h b/common/dialogs/git/dialog_git_commit.h
new file mode 100644
index 0000000000..52e1e6ac6e
--- /dev/null
+++ b/common/dialogs/git/dialog_git_commit.h
@@ -0,0 +1,62 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef DIALOG_GIT_COMMIT_H
+#define DIALOG_GIT_COMMIT_H
+
+#include <dialog_shim.h>
+#include <git2.h>
+
+class wxCheckBox;
+class wxListCtrl;
+class DIALOG_GIT_COMMIT : public DIALOG_SHIM
+{
+public:
+    DIALOG_GIT_COMMIT( wxWindow* parent, git_repository* repo,
+                       const wxString&              defaultAuthorName,
+                       const wxString&              defaultAuthorEmail,
+                       const std::map<wxString, int>& filesToCommit );
+
+    wxString GetCommitMessage() const;
+
+    wxString GetAuthorName() const;
+
+    wxString GetAuthorEmail() const;
+
+    std::vector<wxString> GetSelectedFiles() const;
+
+    void OnTextChanged( wxCommandEvent& event );
+
+private:
+    wxTextCtrl* m_commitMessageTextCtrl;
+    wxTextCtrl* m_authorTextCtrl;
+    wxListCtrl* m_listCtrl;
+    wxButton* m_okButton;
+
+    git_repository* m_repo;
+    wxString m_defaultAuthorName;
+    wxString m_defaultAuthorEmail;
+    std::vector<wxString> m_filesToCommit;
+};
+
+#endif // DIALOG_GIT_COMMIT_H
\ No newline at end of file
diff --git a/common/dialogs/git/dialog_git_progress.cpp b/common/dialogs/git/dialog_git_progress.cpp
new file mode 100644
index 0000000000..633088a5e5
--- /dev/null
+++ b/common/dialogs/git/dialog_git_progress.cpp
@@ -0,0 +1,49 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+#include <wx/progdlg.h>
+#include <wx/statusbr.h>
+
+#include <dialog_shim.h>
+
+class GIT_PROGRESS : public DIALOG_SHIM {
+public:
+    GIT_PROGRESS(wxWindow* aParent, int aMaxValue1, int aMaxValue2);
+    ~GIT_PROGRESS();
+
+    void SetStatusText(const wxString& aText);
+    bool UpdateProgressBar1(int aValue);
+    bool UpdateProgressBar2(int aValue);
+
+private:
+    void OnCancel(wxCommandEvent& aEvent);
+
+    wxGauge* m_progBar1;
+    wxGauge* m_progBar2;
+    wxButton* m_cancelBtn;
+    wxStatusBar* m_statusBar;
+    bool m_cancelled;
+
+    DECLARE_EVENT_TABLE()
+};
diff --git a/common/dialogs/git/dialog_git_repository.cpp b/common/dialogs/git/dialog_git_repository.cpp
new file mode 100644
index 0000000000..fa58270d7f
--- /dev/null
+++ b/common/dialogs/git/dialog_git_repository.cpp
@@ -0,0 +1,448 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "dialog_git_repository.h"
+#include <confirm.h>
+
+#include <git2.h>
+#include <gestfich.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include <wx/clipbrd.h>
+#include <wx/msgdlg.h>
+#include <wx/regex.h>
+#include <wx/stdpaths.h>
+
+
+DIALOG_GIT_REPOSITORY::DIALOG_GIT_REPOSITORY( wxWindow* aParent, git_repository* aRepository, wxString aURL ) :
+        DIALOG_GIT_REPOSITORY_BASE( aParent ), m_repository( aRepository ), m_prevFile( wxEmptyString ),
+        m_tested( 0 ), m_failedTest( false ), m_testError( wxEmptyString ), m_tempRepo( false ),
+        m_repoType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL )
+{
+    m_txtName->SetFocus();
+
+    if( !m_repository )
+    {
+        // Make a temporary repository to test the connection
+        m_tempRepo = true;
+        m_tempPath = wxFileName::CreateTempFileName( "kicadtestrepo" );
+
+        git_repository_init_options options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+        options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
+        git_repository_init_ext( &m_repository, m_tempPath.ToStdString().c_str(), &options );
+    }
+
+    if( !aURL.empty() )
+        m_txtURL->SetValue( aURL );
+    else
+        extractClipboardData();
+
+    if( !m_txtURL->GetValue().IsEmpty() )
+        updateURLData();
+
+    SetupStandardButtons();
+    updateAuthControls();
+}
+
+DIALOG_GIT_REPOSITORY::~DIALOG_GIT_REPOSITORY()
+{
+    if( m_tempRepo )
+    {
+        git_repository_free( m_repository );
+        RmDirRecursive( m_tempPath );
+    }
+}
+
+
+bool DIALOG_GIT_REPOSITORY::extractClipboardData()
+{
+    if( wxTheClipboard->Open() && wxTheClipboard->IsSupported( wxDF_TEXT ) )
+    {
+        wxString clipboardText;
+        wxTextDataObject textData;
+
+        if( wxTheClipboard->GetData( textData ) && !( clipboardText = textData.GetText() ).empty() )
+        {
+            if( std::get<0>( isValidHTTPS( clipboardText ) )
+                || std::get<0>( isValidSSH( clipboardText ) ) )
+                m_txtURL->SetValue( clipboardText );
+        }
+
+        wxTheClipboard->Close();
+    }
+
+    return false;
+}
+
+
+void DIALOG_GIT_REPOSITORY::setDefaultSSHKey()
+{
+    wxFileName sshKey;
+    sshKey.SetPath( wxGetUserHome() );
+    wxString retval;
+
+    sshKey.AppendDir( ".ssh" );
+    sshKey.SetFullName( "id_rsa" );
+
+    if( sshKey.FileExists() )
+    {
+        retval = sshKey.GetFullPath();
+    }
+    else if( sshKey.SetFullName( "id_dsa" ); sshKey.FileExists() )
+    {
+        retval = sshKey.GetFullPath();
+    }
+    else if( sshKey.SetFullName( "id_ecdsa" ); sshKey.FileExists() )
+    {
+        retval = sshKey.GetFullPath();
+    }
+
+    if( !retval.empty() )
+    {
+        m_fpSSHKey->SetFileName( retval );
+        wxFileDirPickerEvent evt;
+        evt.SetPath( retval );
+        OnFileUpdated( evt );
+    }
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnUpdateUI( wxUpdateUIEvent& event )
+{
+    // event.Enable( !m_txtName->GetValue().IsEmpty() && !m_txtURL->GetValue().IsEmpty() );
+}
+
+
+void DIALOG_GIT_REPOSITORY::SetEncrypted( bool aEncrypted )
+{
+    if( aEncrypted )
+    {
+        m_txtPassword->Enable();
+        m_txtPassword->SetToolTip( _( "Enter the password for the SSH key" ) );
+    }
+    else
+    {
+        m_txtPassword->SetValue( wxEmptyString );
+        m_txtPassword->SetToolTip( wxEmptyString );
+        m_txtPassword->Disable();
+    }
+}
+
+std::tuple<bool,wxString,wxString,wxString> DIALOG_GIT_REPOSITORY::isValidHTTPS( const wxString& url )
+{
+    wxRegEx regex( R"((https?:\/\/)(([^:]+)(:([^@]+))?@)?([^\/]+\/[^\s]+))" );
+
+    if( regex.Matches( url ) )
+    {
+        wxString username = regex.GetMatch( url, 3 );
+        wxString password = regex.GetMatch( url, 5 );
+        wxString repoAddress = regex.GetMatch( url, 1 ) + regex.GetMatch( url, 6 );
+        return std::make_tuple( true, username, password, repoAddress );
+    }
+
+    return std::make_tuple( false, "", "", "" );
+}
+
+
+std::tuple<bool,wxString, wxString> DIALOG_GIT_REPOSITORY::isValidSSH( const wxString& url )
+{
+    wxRegEx regex( R"((?:ssh:\/\/)?([^@]+)@([^\/]+\/[^\s]+))" );
+
+    if( regex.Matches( url ) )
+    {
+        wxString username = regex.GetMatch( url, 1 );
+        wxString repoAddress = regex.GetMatch( url, 2 );
+        return std::make_tuple( true, username, repoAddress );
+    }
+
+    return std::make_tuple( false, "", "" );
+}
+
+
+static wxString get_repo_name( wxString& aRepoAddr )
+{
+    wxString retval;
+    size_t last_slash = aRepoAddr.find_last_of( '/' );
+    bool ends_with_dot_git = aRepoAddr.EndsWith( ".git" );
+
+    if( ends_with_dot_git )
+        retval = aRepoAddr.substr( last_slash + 1, aRepoAddr.size() - last_slash - 5 );
+    else
+        retval = aRepoAddr.substr( last_slash + 1, aRepoAddr.size() - last_slash );
+
+    return retval;
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnLocationExit( wxFocusEvent& event )
+{
+    updateURLData();
+    updateAuthControls();
+}
+
+
+void DIALOG_GIT_REPOSITORY::updateURLData()
+{
+    wxString url = m_txtURL->GetValue();
+
+    if( url.IsEmpty() )
+        return;
+
+    if( url.Contains( "https://" ) || url.Contains( "http://" ) )
+    {
+        auto [valid, username, password, repoAddress] = isValidHTTPS( url );
+
+        if( valid )
+        {
+            m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ) );
+            SetUsername( username );
+            SetPassword( password );
+            m_txtURL->SetValue( repoAddress );
+
+            if( m_txtName->GetValue().IsEmpty() )
+                m_txtName->SetValue( get_repo_name( repoAddress ) );
+
+        }
+    }
+    else if( url.Contains( "ssh://" ) || url.Contains( "git@" ) )
+    {
+        auto [valid, username, repoAddress] = isValidSSH( url );
+
+        if( valid )
+        {
+            m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) );
+            m_txtUsername->SetValue( username );
+            m_txtURL->SetValue( repoAddress );
+
+            if( m_txtName->GetValue().IsEmpty() )
+                m_txtName->SetValue( get_repo_name( repoAddress ) );
+
+            setDefaultSSHKey();
+        }
+    }
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnTestClick( wxCommandEvent& event )
+{
+    git_remote* remote = nullptr;
+    git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+    // We track if we have already tried to connect.
+    // If we have, the server may come back to offer another connection
+    // type, so we need to keep track of how many times we have tried.
+    m_tested = 0;
+
+    callbacks.credentials = []( git_cred** aOut, const char* aUrl, const char* aUsername,
+                                unsigned int aAllowedTypes, void* aPayload ) -> int
+    {
+        DIALOG_GIT_REPOSITORY* dialog = static_cast<DIALOG_GIT_REPOSITORY*>( aPayload );
+
+        if( dialog->GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL )
+            return GIT_PASSTHROUGH;
+
+        if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
+            && !( dialog->GetTested() & GIT_CREDTYPE_USERNAME ) )
+        {
+            wxString username = dialog->GetUsername().Trim().Trim( false );
+            git_cred_username_new( aOut, username.ToStdString().c_str() );
+            dialog->GetTested() |= GIT_CREDTYPE_USERNAME;
+        }
+        else if( dialog->GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS
+                 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
+                 && !( dialog->GetTested() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
+        {
+            wxString username = dialog->GetUsername().Trim().Trim( false );
+            wxString password = dialog->GetPassword().Trim().Trim( false );
+
+            git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
+                                             password.ToStdString().c_str() );
+            dialog->GetTested() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+        }
+        else if( dialog->GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH
+                 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
+                 && !( dialog->GetTested() & GIT_CREDTYPE_SSH_KEY ) )
+        {
+            // SSH key authentication
+            wxString sshKey = dialog->GetRepoSSHPath();
+            wxString sshPubKey = sshKey + ".pub";
+            wxString username = dialog->GetUsername().Trim().Trim( false );
+            wxString password = dialog->GetPassword().Trim().Trim( false );
+
+            git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
+                                  sshPubKey.ToStdString().c_str(), sshKey.ToStdString().c_str(),
+                                  password.ToStdString().c_str() );
+            dialog->GetTested() |= GIT_CREDTYPE_SSH_KEY;
+        }
+        else
+        {
+            return GIT_PASSTHROUGH;
+        }
+
+        return GIT_OK;
+    };
+
+    callbacks.payload = this;
+
+    git_remote_create_with_fetchspec( &remote, m_repository, "origin", m_txtURL->GetValue(),
+                                      "+refs/heads/*:refs/remotes/origin/*" );
+
+    if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &callbacks, nullptr, nullptr ) != GIT_OK )
+        SetTestResult( true, git_error_last()->message );
+    else
+        SetTestResult( false, wxEmptyString );
+
+    git_remote_disconnect( remote );
+    git_remote_free( remote );
+
+    auto dlg = wxMessageDialog( this, wxEmptyString, _( "Test connection" ), wxOK | wxICON_INFORMATION );
+
+    if( !m_failedTest )
+    {
+        dlg.SetMessage( _( "Connection successful" ) );
+    }
+    else
+    {
+        dlg.SetMessage( wxString::Format( _( "Could not connect to '%s' " ), m_txtURL->GetValue() ) );
+        dlg.SetExtendedMessage( m_testError );
+    }
+
+    dlg.ShowModal();
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnFileUpdated( wxFileDirPickerEvent& aEvent )
+{
+    wxString file = aEvent.GetPath();
+
+    if( file.ends_with( ".pub" ) )
+        file = file.Left( file.size() - 4 );
+
+    std::ifstream ifs( file.ToStdString() );
+
+    if( !ifs.good() || !ifs.is_open() )
+    {
+        DisplayErrorMessage( this, wxString::Format( _( "Could not open private key '%s'" ), file ),
+                             wxString::Format( "%s: %d", std::strerror( errno ), errno ) );
+        return;
+    }
+
+    std::string line;
+    std::getline( ifs, line );
+
+    bool isValid = ( line.find( "PRIVATE KEY" ) != std::string::npos );
+    bool isEncrypted = ( line.find( "ENCRYPTED" ) != std::string::npos );
+
+    if( !isValid )
+    {
+        DisplayErrorMessage( this, _( "Invalid SSH Key" ), _( "The selected file is not a valid SSH private key" ) );
+        CallAfter( [this] { SetRepoSSHPath( m_prevFile ); } );
+        return;
+    }
+
+    if( isEncrypted )
+    {
+        m_txtPassword->Enable();
+        m_txtPassword->SetToolTip( _( "Enter the password for the SSH key" ) );
+    }
+    else
+    {
+        m_txtPassword->SetValue( wxEmptyString );
+        m_txtPassword->SetToolTip( wxEmptyString );
+        m_txtPassword->Disable();
+    }
+
+    ifs.close();
+
+    std::ifstream pubIfs( file + ".pub" );
+
+    if( !pubIfs.good() || !pubIfs.is_open() )
+    {
+        DisplayErrorMessage( this, wxString::Format( _( "Could not open public key '%s'" ), file + ".pub" ),
+                             wxString::Format( "%s: %d", std::strerror( errno ), errno ) );
+        aEvent.SetPath( wxEmptyString );
+        CallAfter( [this] { SetRepoSSHPath( m_prevFile ); } );
+        return;
+    }
+
+    m_prevFile = file;
+    pubIfs.close();
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnOKClick( wxCommandEvent& event )
+{
+    // Save the repository details
+
+    if( m_txtName->GetValue().IsEmpty() )
+    {
+        DisplayErrorMessage( this, _( "Missing information" ), _( "Please enter a name for the repository" ) );
+        return;
+    }
+
+    if( m_txtURL->GetValue().IsEmpty() )
+    {
+        DisplayErrorMessage( this, _( "Missing information" ), _( "Please enter a URL for the repository" ) );
+        return;
+    }
+
+    EndModal( wxID_OK );
+}
+
+void DIALOG_GIT_REPOSITORY::updateAuthControls()
+{
+    if( m_ConnType->GetSelection() == static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL ) )
+    {
+        m_panelAuth->Show( false );
+    }
+    else
+    {
+        m_panelAuth->Show( true );
+
+        if( m_ConnType->GetSelection() == static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) )
+        {
+            m_fpSSHKey->Show( true );
+            m_labelSSH->Show( true );
+            m_labelPass1->SetLabel( _( "SSH Key Password" ) );
+        }
+        else
+        {
+            m_fpSSHKey->Show( false );
+            m_labelSSH->Show( false );
+            m_labelPass1->SetLabel( _( "Password" ) );
+            setDefaultSSHKey();
+        }
+    }
+}
+
+
+void DIALOG_GIT_REPOSITORY::OnSelectConnType( wxCommandEvent& event )
+{
+    updateAuthControls();
+}
diff --git a/common/dialogs/git/dialog_git_repository.h b/common/dialogs/git/dialog_git_repository.h
new file mode 100644
index 0000000000..8f4abd5c38
--- /dev/null
+++ b/common/dialogs/git/dialog_git_repository.h
@@ -0,0 +1,124 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef DIALOG_GIT_REPOSITORY_H_
+#define DIALOG_GIT_REPOSITORY_H_
+
+#include "dialog_git_repository_base.h"
+
+#include <git/kicad_git_common.h>
+#include <git2.h>
+
+class DIALOG_GIT_REPOSITORY : public DIALOG_GIT_REPOSITORY_BASE
+{
+public:
+    DIALOG_GIT_REPOSITORY( wxWindow* aParent, git_repository* aRepository, wxString aURL = wxEmptyString );
+    ~DIALOG_GIT_REPOSITORY() override;
+
+    void SetTestResult( bool aFailed, const wxString& aError )
+    {
+        m_failedTest = aFailed;
+        m_testError = aError;
+    }
+
+    void SetRepoType( KIGIT_COMMON::GIT_CONN_TYPE aType )
+    {
+        m_ConnType->SetSelection( static_cast<int>( aType ) );
+        updateAuthControls();
+    }
+
+    KIGIT_COMMON::GIT_CONN_TYPE GetRepoType() const { return static_cast<KIGIT_COMMON::GIT_CONN_TYPE>( m_ConnType->GetSelection() ); }
+
+    void     SetRepoName( const wxString& aName ) { m_txtName->SetValue( aName ); }
+    wxString GetRepoName() const { return m_txtName->GetValue(); }
+
+    void     SetRepoURL( const wxString& aURL ) { m_txtURL->SetValue( aURL ); }
+    wxString GetRepoURL() const { return m_txtURL->GetValue(); }
+
+    /**
+     * @brief Get the Bare Repo U R L object
+     *
+     * @return wxString without the protocol
+     */
+    wxString GetBareRepoURL() const
+    {
+        wxString url = m_txtURL->GetValue();
+
+        if( url.StartsWith( "https://" ) )
+            url = url.Mid( 8 );
+        else if( url.StartsWith( "http://" ) )
+            url = url.Mid( 7 );
+        else if( url.StartsWith( "ssh://" ) )
+            url = url.Mid( 6 );
+
+        return url;
+    }
+
+    void     SetUsername( const wxString& aUsername ) { m_txtUsername->SetValue( aUsername ); }
+    wxString GetUsername() const { return m_txtUsername->GetValue(); }
+
+    void     SetPassword( const wxString& aPassword ) { m_txtPassword->SetValue( aPassword ); }
+    wxString GetPassword() const { return m_txtPassword->GetValue(); }
+
+    void     SetRepoSSHPath( const wxString& aPath ) { m_fpSSHKey->SetFileName( aPath ); m_prevFile = aPath; }
+    wxString GetRepoSSHPath() const { return m_fpSSHKey->GetFileName().GetFullPath(); }
+
+    unsigned& GetTested() { return m_tested; }
+
+    void     SetEncrypted( bool aEncrypted = true );
+
+private:
+    void OnUpdateUI( wxUpdateUIEvent& event ) override;
+    void OnLocationExit( wxFocusEvent& event ) override;
+    void OnOKClick( wxCommandEvent& event ) override;
+
+    void OnSelectConnType( wxCommandEvent& event ) override;
+    void OnTestClick( wxCommandEvent& event ) override;
+
+    void OnFileUpdated( wxFileDirPickerEvent& event ) override;
+
+    void setDefaultSSHKey();
+
+    void updateAuthControls();
+    void updateURLData();
+    bool extractClipboardData();
+
+    std::tuple<bool,wxString,wxString,wxString> isValidHTTPS( const wxString& url );
+    std::tuple<bool,wxString, wxString> isValidSSH( const wxString& url );
+
+    git_repository* m_repository;
+
+    wxString m_prevFile;
+
+    unsigned m_tested;
+    bool m_failedTest;
+    wxString m_testError;
+
+    bool m_tempRepo;
+    wxString m_tempPath;
+
+    KIGIT_COMMON::GIT_CONN_TYPE m_repoType;
+
+};
+
+#endif /* DIALOG_GIT_REPOSITORY_H_ */
\ No newline at end of file
diff --git a/common/dialogs/git/dialog_git_repository_base.cpp b/common/dialogs/git/dialog_git_repository_base.cpp
new file mode 100644
index 0000000000..332dbc1d7f
--- /dev/null
+++ b/common/dialogs/git/dialog_git_repository_base.cpp
@@ -0,0 +1,172 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-254-gc2ef7767)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "dialog_git_repository_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+DIALOG_GIT_REPOSITORY_BASE::DIALOG_GIT_REPOSITORY_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : DIALOG_SHIM( parent, id, title, pos, size, style )
+{
+	this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize );
+
+	wxBoxSizer* bSizerMain;
+	bSizerMain = new wxBoxSizer( wxVERTICAL );
+
+	m_staticText1 = new wxStaticText( this, wxID_ANY, _("Connection"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText1->Wrap( -1 );
+	bSizerMain->Add( m_staticText1, 0, wxLEFT|wxTOP, 10 );
+
+	m_staticline1 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+	bSizerMain->Add( m_staticline1, 0, wxEXPAND|wxTOP, 5 );
+
+	wxFlexGridSizer* fgSizer2;
+	fgSizer2 = new wxFlexGridSizer( 0, 2, 0, 0 );
+	fgSizer2->AddGrowableCol( 1 );
+	fgSizer2->SetFlexibleDirection( wxBOTH );
+	fgSizer2->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
+
+	m_staticText3 = new wxStaticText( this, wxID_ANY, _("Name"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText3->Wrap( -1 );
+	fgSizer2->Add( m_staticText3, 0, wxALL, 5 );
+
+	m_txtName = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizer2->Add( m_txtName, 0, wxALL|wxEXPAND, 5 );
+
+	m_staticText4 = new wxStaticText( this, wxID_ANY, _("Location"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText4->Wrap( -1 );
+	fgSizer2->Add( m_staticText4, 0, wxALL, 5 );
+
+	m_txtURL = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizer2->Add( m_txtURL, 0, wxALL|wxEXPAND, 5 );
+
+	m_staticText9 = new wxStaticText( this, wxID_ANY, _("Connection Type"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText9->Wrap( -1 );
+	fgSizer2->Add( m_staticText9, 0, wxALL, 5 );
+
+	wxBoxSizer* bSizer3;
+	bSizer3 = new wxBoxSizer( wxHORIZONTAL );
+
+	wxString m_ConnTypeChoices[] = { _("HTTPS"), _("SSH"), _("Local") };
+	int m_ConnTypeNChoices = sizeof( m_ConnTypeChoices ) / sizeof( wxString );
+	m_ConnType = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_ConnTypeNChoices, m_ConnTypeChoices, 0 );
+	m_ConnType->SetSelection( 0 );
+	bSizer3->Add( m_ConnType, 1, wxEXPAND|wxLEFT|wxRIGHT, 5 );
+
+
+	bSizer3->Add( 0, 0, 1, wxEXPAND, 5 );
+
+
+	fgSizer2->Add( bSizer3, 1, wxEXPAND, 5 );
+
+
+	bSizerMain->Add( fgSizer2, 1, wxEXPAND, 5 );
+
+	m_panelAuth = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* m_szAuth;
+	m_szAuth = new wxBoxSizer( wxVERTICAL );
+
+
+	m_szAuth->Add( 0, 0, 1, wxEXPAND, 5 );
+
+	wxBoxSizer* bSizer11;
+	bSizer11 = new wxBoxSizer( wxHORIZONTAL );
+
+	m_staticText2 = new wxStaticText( m_panelAuth, wxID_ANY, _("Authentication"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText2->Wrap( -1 );
+	bSizer11->Add( m_staticText2, 0, wxLEFT|wxTOP, 10 );
+
+
+	bSizer11->Add( 0, 0, 1, wxEXPAND, 5 );
+
+
+	m_szAuth->Add( bSizer11, 0, wxEXPAND, 5 );
+
+	m_staticline2 = new wxStaticLine( m_panelAuth, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+	m_szAuth->Add( m_staticline2, 0, wxEXPAND | wxALL, 5 );
+
+	wxFlexGridSizer* fgSshSizer;
+	fgSshSizer = new wxFlexGridSizer( 0, 2, 0, 0 );
+	fgSshSizer->AddGrowableCol( 1 );
+	fgSshSizer->SetFlexibleDirection( wxBOTH );
+	fgSshSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
+
+	m_labelSSH = new wxStaticText( m_panelAuth, wxID_ANY, _("SSH Private Key"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_labelSSH->Wrap( -1 );
+	fgSshSizer->Add( m_labelSSH, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5 );
+
+	wxBoxSizer* bSizer41;
+	bSizer41 = new wxBoxSizer( wxHORIZONTAL );
+
+	m_fpSSHKey = new wxFilePickerCtrl( m_panelAuth, wxID_ANY, wxEmptyString, _("Select SSH private key file"), _("*"), wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE|wxFLP_FILE_MUST_EXIST|wxFLP_OPEN );
+	bSizer41->Add( m_fpSSHKey, 1, wxEXPAND|wxLEFT|wxRESERVE_SPACE_EVEN_IF_HIDDEN|wxRIGHT, 5 );
+
+	m_btnTest = new wxButton( m_panelAuth, wxID_ANY, _("Test"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer41->Add( m_btnTest, 0, wxLEFT|wxRIGHT, 5 );
+
+
+	fgSshSizer->Add( bSizer41, 1, wxEXPAND, 5 );
+
+	m_staticText11 = new wxStaticText( m_panelAuth, wxID_ANY, _("Username"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText11->Wrap( -1 );
+	fgSshSizer->Add( m_staticText11, 0, wxALL, 5 );
+
+	m_txtUsername = new wxTextCtrl( m_panelAuth, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSshSizer->Add( m_txtUsername, 0, wxALL|wxEXPAND, 5 );
+
+	m_labelPass1 = new wxStaticText( m_panelAuth, wxID_ANY, _("SSH Key Password"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_labelPass1->Wrap( -1 );
+	fgSshSizer->Add( m_labelPass1, 0, wxALL, 5 );
+
+	m_txtPassword = new wxTextCtrl( m_panelAuth, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSshSizer->Add( m_txtPassword, 0, wxALL|wxEXPAND, 5 );
+
+
+	m_szAuth->Add( fgSshSizer, 1, wxEXPAND, 5 );
+
+
+	m_panelAuth->SetSizer( m_szAuth );
+	m_panelAuth->Layout();
+	m_szAuth->Fit( m_panelAuth );
+	bSizerMain->Add( m_panelAuth, 1, wxALL|wxEXPAND|wxRESERVE_SPACE_EVEN_IF_HIDDEN, 0 );
+
+	m_sdbSizer = new wxStdDialogButtonSizer();
+	m_sdbSizerOK = new wxButton( this, wxID_OK );
+	m_sdbSizer->AddButton( m_sdbSizerOK );
+	m_sdbSizerCancel = new wxButton( this, wxID_CANCEL );
+	m_sdbSizer->AddButton( m_sdbSizerCancel );
+	m_sdbSizer->Realize();
+
+	bSizerMain->Add( m_sdbSizer, 0, wxALL|wxEXPAND, 5 );
+
+
+	this->SetSizer( bSizerMain );
+	this->Layout();
+
+	this->Centre( wxBOTH );
+
+	// Connect Events
+	this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnClose ) );
+	this->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnUpdateUI ) );
+	m_txtURL->Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnLocationExit ), NULL, this );
+	m_ConnType->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnSelectConnType ), NULL, this );
+	m_fpSSHKey->Connect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnFileUpdated ), NULL, this );
+	m_btnTest->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnTestClick ), NULL, this );
+	m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnOKClick ), NULL, this );
+}
+
+DIALOG_GIT_REPOSITORY_BASE::~DIALOG_GIT_REPOSITORY_BASE()
+{
+	// Disconnect Events
+	this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnClose ) );
+	this->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnUpdateUI ) );
+	m_txtURL->Disconnect( wxEVT_KILL_FOCUS, wxFocusEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnLocationExit ), NULL, this );
+	m_ConnType->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnSelectConnType ), NULL, this );
+	m_fpSSHKey->Disconnect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnFileUpdated ), NULL, this );
+	m_btnTest->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnTestClick ), NULL, this );
+	m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GIT_REPOSITORY_BASE::OnOKClick ), NULL, this );
+
+}
diff --git a/common/dialogs/git/dialog_git_repository_base.fbp b/common/dialogs/git/dialog_git_repository_base.fbp
new file mode 100644
index 0000000000..ede00eae2d
--- /dev/null
+++ b/common/dialogs/git/dialog_git_repository_base.fbp
@@ -0,0 +1,1335 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="16" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">dialog_git_repository_base</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="image_path_wrapper_function_name"></property>
+        <property name="indent_with_spaces"></property>
+        <property name="internationalize">1</property>
+        <property name="name">DIALOG_GIT_REPOSITORY_BASE</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_array_enum">0</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Dialog" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="center">wxBOTH</property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="drag_accept_files">0</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="extra_style"></property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size">-1,-1</property>
+            <property name="name">DIALOG_GIT_REPOSITORY_BASE</property>
+            <property name="pos"></property>
+            <property name="size">682,598</property>
+            <property name="style">wxCAPTION|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER</property>
+            <property name="subclass">DIALOG_SHIM; dialog_shim.h</property>
+            <property name="title">Git Repository</property>
+            <property name="tooltip"></property>
+            <property name="two_step_creation">0</property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style"></property>
+            <event name="OnClose">OnClose</event>
+            <event name="OnUpdateUI">OnUpdateUI</event>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bSizerMain</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="0">
+                    <property name="border">10</property>
+                    <property name="flag">wxLEFT|wxTOP</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStaticText" expanded="0">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="drag_accept_files">0</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label">Connection</property>
+                        <property name="markup">0</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_staticText1</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <property name="wrap">-1</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="0">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND|wxTOP</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStaticLine" expanded="0">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="drag_accept_files">0</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_staticline1</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style">wxLI_HORIZONTAL</property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="wxFlexGridSizer" expanded="1">
+                        <property name="cols">2</property>
+                        <property name="flexible_direction">wxBOTH</property>
+                        <property name="growablecols">1</property>
+                        <property name="growablerows"></property>
+                        <property name="hgap">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="name">fgSizer2</property>
+                        <property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
+                        <property name="permission">none</property>
+                        <property name="rows">0</property>
+                        <property name="vgap">0</property>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticText" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Name</property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticText3</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <property name="wrap">-1</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL|wxEXPAND</property>
+                            <property name="proportion">0</property>
+                            <object class="wxTextCtrl" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="maxlength"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_txtName</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="value"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticText" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Location</property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticText4</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <property name="wrap">-1</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL|wxEXPAND</property>
+                            <property name="proportion">0</property>
+                            <object class="wxTextCtrl" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="maxlength"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_txtURL</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="value"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnKillFocus">OnLocationExit</event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticText" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Connection Type</property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticText9</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <property name="wrap">-1</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxBoxSizer" expanded="1">
+                                <property name="minimum_size"></property>
+                                <property name="name">bSizer3</property>
+                                <property name="orient">wxHORIZONTAL</property>
+                                <property name="permission">none</property>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxEXPAND|wxLEFT|wxRIGHT</property>
+                                    <property name="proportion">1</property>
+                                    <object class="wxChoice" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="choices">&quot;HTTPS&quot; &quot;SSH&quot; &quot;Local&quot;</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_ConnType</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="selection">0</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnChoice">OnSelectConnType</event>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxEXPAND</property>
+                                    <property name="proportion">1</property>
+                                    <object class="spacer" expanded="0">
+                                        <property name="height">0</property>
+                                        <property name="permission">protected</property>
+                                        <property name="width">0</property>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">0</property>
+                    <property name="flag">wxALL|wxEXPAND|wxRESERVE_SPACE_EVEN_IF_HIDDEN</property>
+                    <property name="proportion">1</property>
+                    <object class="wxPanel" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="drag_accept_files">0</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_panelAuth</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style">wxTAB_TRAVERSAL</property>
+                        <object class="wxBoxSizer" expanded="1">
+                            <property name="minimum_size"></property>
+                            <property name="name">m_szAuth</property>
+                            <property name="orient">wxVERTICAL</property>
+                            <property name="permission">none</property>
+                            <object class="sizeritem" expanded="0">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND</property>
+                                <property name="proportion">1</property>
+                                <object class="spacer" expanded="0">
+                                    <property name="height">0</property>
+                                    <property name="permission">protected</property>
+                                    <property name="width">0</property>
+                                </object>
+                            </object>
+                            <object class="sizeritem" expanded="1">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND</property>
+                                <property name="proportion">0</property>
+                                <object class="wxBoxSizer" expanded="1">
+                                    <property name="minimum_size"></property>
+                                    <property name="name">bSizer11</property>
+                                    <property name="orient">wxHORIZONTAL</property>
+                                    <property name="permission">none</property>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">10</property>
+                                        <property name="flag">wxLEFT|wxTOP</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxStaticText" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label">Authentication</property>
+                                            <property name="markup">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_staticText2</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                            <property name="wrap">-1</property>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxEXPAND</property>
+                                        <property name="proportion">1</property>
+                                        <object class="spacer" expanded="0">
+                                            <property name="height">0</property>
+                                            <property name="permission">protected</property>
+                                            <property name="width">0</property>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                            <object class="sizeritem" expanded="0">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND | wxALL</property>
+                                <property name="proportion">0</property>
+                                <object class="wxStaticLine" expanded="0">
+                                    <property name="BottomDockable">1</property>
+                                    <property name="LeftDockable">1</property>
+                                    <property name="RightDockable">1</property>
+                                    <property name="TopDockable">1</property>
+                                    <property name="aui_layer"></property>
+                                    <property name="aui_name"></property>
+                                    <property name="aui_position"></property>
+                                    <property name="aui_row"></property>
+                                    <property name="best_size"></property>
+                                    <property name="bg"></property>
+                                    <property name="caption"></property>
+                                    <property name="caption_visible">1</property>
+                                    <property name="center_pane">0</property>
+                                    <property name="close_button">1</property>
+                                    <property name="context_help"></property>
+                                    <property name="context_menu">1</property>
+                                    <property name="default_pane">0</property>
+                                    <property name="dock">Dock</property>
+                                    <property name="dock_fixed">0</property>
+                                    <property name="docking">Left</property>
+                                    <property name="drag_accept_files">0</property>
+                                    <property name="enabled">1</property>
+                                    <property name="fg"></property>
+                                    <property name="floatable">1</property>
+                                    <property name="font"></property>
+                                    <property name="gripper">0</property>
+                                    <property name="hidden">0</property>
+                                    <property name="id">wxID_ANY</property>
+                                    <property name="max_size"></property>
+                                    <property name="maximize_button">0</property>
+                                    <property name="maximum_size"></property>
+                                    <property name="min_size"></property>
+                                    <property name="minimize_button">0</property>
+                                    <property name="minimum_size"></property>
+                                    <property name="moveable">1</property>
+                                    <property name="name">m_staticline2</property>
+                                    <property name="pane_border">1</property>
+                                    <property name="pane_position"></property>
+                                    <property name="pane_size"></property>
+                                    <property name="permission">protected</property>
+                                    <property name="pin_button">1</property>
+                                    <property name="pos"></property>
+                                    <property name="resize">Resizable</property>
+                                    <property name="show">1</property>
+                                    <property name="size"></property>
+                                    <property name="style">wxLI_HORIZONTAL</property>
+                                    <property name="subclass">; ; forward_declare</property>
+                                    <property name="toolbar_pane">0</property>
+                                    <property name="tooltip"></property>
+                                    <property name="window_extra_style"></property>
+                                    <property name="window_name"></property>
+                                    <property name="window_style"></property>
+                                </object>
+                            </object>
+                            <object class="sizeritem" expanded="1">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND</property>
+                                <property name="proportion">1</property>
+                                <object class="wxFlexGridSizer" expanded="1">
+                                    <property name="cols">2</property>
+                                    <property name="flexible_direction">wxBOTH</property>
+                                    <property name="growablecols">1</property>
+                                    <property name="growablerows"></property>
+                                    <property name="hgap">0</property>
+                                    <property name="minimum_size"></property>
+                                    <property name="name">fgSshSizer</property>
+                                    <property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
+                                    <property name="permission">none</property>
+                                    <property name="rows">0</property>
+                                    <property name="vgap">0</property>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALIGN_CENTER_VERTICAL|wxALL|wxRESERVE_SPACE_EVEN_IF_HIDDEN</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxStaticText" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label">SSH Private Key</property>
+                                            <property name="markup">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_labelSSH</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                            <property name="wrap">-1</property>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="1">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxEXPAND</property>
+                                        <property name="proportion">1</property>
+                                        <object class="wxBoxSizer" expanded="1">
+                                            <property name="minimum_size"></property>
+                                            <property name="name">bSizer41</property>
+                                            <property name="orient">wxHORIZONTAL</property>
+                                            <property name="permission">none</property>
+                                            <object class="sizeritem" expanded="0">
+                                                <property name="border">5</property>
+                                                <property name="flag">wxEXPAND|wxLEFT|wxRESERVE_SPACE_EVEN_IF_HIDDEN|wxRIGHT</property>
+                                                <property name="proportion">1</property>
+                                                <object class="wxFilePickerCtrl" expanded="0">
+                                                    <property name="BottomDockable">1</property>
+                                                    <property name="LeftDockable">1</property>
+                                                    <property name="RightDockable">1</property>
+                                                    <property name="TopDockable">1</property>
+                                                    <property name="aui_layer"></property>
+                                                    <property name="aui_name"></property>
+                                                    <property name="aui_position"></property>
+                                                    <property name="aui_row"></property>
+                                                    <property name="best_size"></property>
+                                                    <property name="bg"></property>
+                                                    <property name="caption"></property>
+                                                    <property name="caption_visible">1</property>
+                                                    <property name="center_pane">0</property>
+                                                    <property name="close_button">1</property>
+                                                    <property name="context_help"></property>
+                                                    <property name="context_menu">1</property>
+                                                    <property name="default_pane">0</property>
+                                                    <property name="dock">Dock</property>
+                                                    <property name="dock_fixed">0</property>
+                                                    <property name="docking">Left</property>
+                                                    <property name="drag_accept_files">0</property>
+                                                    <property name="enabled">1</property>
+                                                    <property name="fg"></property>
+                                                    <property name="floatable">1</property>
+                                                    <property name="font"></property>
+                                                    <property name="gripper">0</property>
+                                                    <property name="hidden">0</property>
+                                                    <property name="id">wxID_ANY</property>
+                                                    <property name="max_size"></property>
+                                                    <property name="maximize_button">0</property>
+                                                    <property name="maximum_size"></property>
+                                                    <property name="message">Select SSH private key file</property>
+                                                    <property name="min_size"></property>
+                                                    <property name="minimize_button">0</property>
+                                                    <property name="minimum_size"></property>
+                                                    <property name="moveable">1</property>
+                                                    <property name="name">m_fpSSHKey</property>
+                                                    <property name="pane_border">1</property>
+                                                    <property name="pane_position"></property>
+                                                    <property name="pane_size"></property>
+                                                    <property name="permission">protected</property>
+                                                    <property name="pin_button">1</property>
+                                                    <property name="pos"></property>
+                                                    <property name="resize">Resizable</property>
+                                                    <property name="show">1</property>
+                                                    <property name="size"></property>
+                                                    <property name="style">wxFLP_DEFAULT_STYLE|wxFLP_FILE_MUST_EXIST|wxFLP_OPEN</property>
+                                                    <property name="subclass"></property>
+                                                    <property name="toolbar_pane">0</property>
+                                                    <property name="tooltip"></property>
+                                                    <property name="validator_data_type"></property>
+                                                    <property name="validator_style">wxFILTER_NONE</property>
+                                                    <property name="validator_type">wxDefaultValidator</property>
+                                                    <property name="validator_variable"></property>
+                                                    <property name="value"></property>
+                                                    <property name="wildcard">*</property>
+                                                    <property name="window_extra_style"></property>
+                                                    <property name="window_name"></property>
+                                                    <property name="window_style"></property>
+                                                    <event name="OnFileChanged">OnFileUpdated</event>
+                                                </object>
+                                            </object>
+                                            <object class="sizeritem" expanded="0">
+                                                <property name="border">5</property>
+                                                <property name="flag">wxLEFT|wxRIGHT</property>
+                                                <property name="proportion">0</property>
+                                                <object class="wxButton" expanded="0">
+                                                    <property name="BottomDockable">1</property>
+                                                    <property name="LeftDockable">1</property>
+                                                    <property name="RightDockable">1</property>
+                                                    <property name="TopDockable">1</property>
+                                                    <property name="aui_layer"></property>
+                                                    <property name="aui_name"></property>
+                                                    <property name="aui_position"></property>
+                                                    <property name="aui_row"></property>
+                                                    <property name="auth_needed">0</property>
+                                                    <property name="best_size"></property>
+                                                    <property name="bg"></property>
+                                                    <property name="bitmap"></property>
+                                                    <property name="caption"></property>
+                                                    <property name="caption_visible">1</property>
+                                                    <property name="center_pane">0</property>
+                                                    <property name="close_button">1</property>
+                                                    <property name="context_help"></property>
+                                                    <property name="context_menu">1</property>
+                                                    <property name="current"></property>
+                                                    <property name="default">0</property>
+                                                    <property name="default_pane">0</property>
+                                                    <property name="disabled"></property>
+                                                    <property name="dock">Dock</property>
+                                                    <property name="dock_fixed">0</property>
+                                                    <property name="docking">Left</property>
+                                                    <property name="drag_accept_files">0</property>
+                                                    <property name="enabled">1</property>
+                                                    <property name="fg"></property>
+                                                    <property name="floatable">1</property>
+                                                    <property name="focus"></property>
+                                                    <property name="font"></property>
+                                                    <property name="gripper">0</property>
+                                                    <property name="hidden">0</property>
+                                                    <property name="id">wxID_ANY</property>
+                                                    <property name="label">Test</property>
+                                                    <property name="margins"></property>
+                                                    <property name="markup">0</property>
+                                                    <property name="max_size"></property>
+                                                    <property name="maximize_button">0</property>
+                                                    <property name="maximum_size"></property>
+                                                    <property name="min_size"></property>
+                                                    <property name="minimize_button">0</property>
+                                                    <property name="minimum_size"></property>
+                                                    <property name="moveable">1</property>
+                                                    <property name="name">m_btnTest</property>
+                                                    <property name="pane_border">1</property>
+                                                    <property name="pane_position"></property>
+                                                    <property name="pane_size"></property>
+                                                    <property name="permission">protected</property>
+                                                    <property name="pin_button">1</property>
+                                                    <property name="pos"></property>
+                                                    <property name="position"></property>
+                                                    <property name="pressed"></property>
+                                                    <property name="resize">Resizable</property>
+                                                    <property name="show">1</property>
+                                                    <property name="size"></property>
+                                                    <property name="style"></property>
+                                                    <property name="subclass">; ; forward_declare</property>
+                                                    <property name="toolbar_pane">0</property>
+                                                    <property name="tooltip"></property>
+                                                    <property name="validator_data_type"></property>
+                                                    <property name="validator_style">wxFILTER_NONE</property>
+                                                    <property name="validator_type">wxDefaultValidator</property>
+                                                    <property name="validator_variable"></property>
+                                                    <property name="window_extra_style"></property>
+                                                    <property name="window_name"></property>
+                                                    <property name="window_style"></property>
+                                                    <event name="OnButtonClick">OnTestClick</event>
+                                                </object>
+                                            </object>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxStaticText" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label">Username</property>
+                                            <property name="markup">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_staticText11</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                            <property name="wrap">-1</property>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL|wxEXPAND</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxTextCtrl" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="maxlength"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_txtUsername</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="validator_data_type"></property>
+                                            <property name="validator_style">wxFILTER_NONE</property>
+                                            <property name="validator_type">wxDefaultValidator</property>
+                                            <property name="validator_variable"></property>
+                                            <property name="value"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxStaticText" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label">SSH Key Password</property>
+                                            <property name="markup">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_labelPass1</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                            <property name="wrap">-1</property>
+                                        </object>
+                                    </object>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL|wxEXPAND</property>
+                                        <property name="proportion">0</property>
+                                        <object class="wxTextCtrl" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">0</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_accept_files">0</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">1</property>
+                                            <property name="font"></property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="maxlength"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">1</property>
+                                            <property name="name">m_txtPassword</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Resizable</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="style"></property>
+                                            <property name="subclass">; ; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="validator_data_type"></property>
+                                            <property name="validator_style">wxFILTER_NONE</property>
+                                            <property name="validator_type">wxDefaultValidator</property>
+                                            <property name="validator_variable"></property>
+                                            <property name="value"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="0">
+                    <property name="border">5</property>
+                    <property name="flag">wxALL|wxEXPAND</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStdDialogButtonSizer" expanded="0">
+                        <property name="Apply">0</property>
+                        <property name="Cancel">1</property>
+                        <property name="ContextHelp">0</property>
+                        <property name="Help">0</property>
+                        <property name="No">0</property>
+                        <property name="OK">1</property>
+                        <property name="Save">0</property>
+                        <property name="Yes">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="name">m_sdbSizer</property>
+                        <property name="permission">protected</property>
+                        <event name="OnOKButtonClick">OnOKClick</event>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/common/dialogs/git/dialog_git_repository_base.h b/common/dialogs/git/dialog_git_repository_base.h
new file mode 100644
index 0000000000..0ba8c329c7
--- /dev/null
+++ b/common/dialogs/git/dialog_git_repository_base.h
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-254-gc2ef7767)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/intl.h>
+#include "dialog_shim.h"
+#include <wx/string.h>
+#include <wx/stattext.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/statline.h>
+#include <wx/textctrl.h>
+#include <wx/choice.h>
+#include <wx/sizer.h>
+#include <wx/filepicker.h>
+#include <wx/button.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/panel.h>
+#include <wx/dialog.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class DIALOG_GIT_REPOSITORY_BASE
+///////////////////////////////////////////////////////////////////////////////
+class DIALOG_GIT_REPOSITORY_BASE : public DIALOG_SHIM
+{
+	private:
+
+	protected:
+		wxStaticText* m_staticText1;
+		wxStaticLine* m_staticline1;
+		wxStaticText* m_staticText3;
+		wxTextCtrl* m_txtName;
+		wxStaticText* m_staticText4;
+		wxTextCtrl* m_txtURL;
+		wxStaticText* m_staticText9;
+		wxChoice* m_ConnType;
+		wxPanel* m_panelAuth;
+		wxStaticText* m_staticText2;
+		wxStaticLine* m_staticline2;
+		wxStaticText* m_labelSSH;
+		wxFilePickerCtrl* m_fpSSHKey;
+		wxButton* m_btnTest;
+		wxStaticText* m_staticText11;
+		wxTextCtrl* m_txtUsername;
+		wxStaticText* m_labelPass1;
+		wxTextCtrl* m_txtPassword;
+		wxStdDialogButtonSizer* m_sdbSizer;
+		wxButton* m_sdbSizerOK;
+		wxButton* m_sdbSizerCancel;
+
+		// Virtual event handlers, override them in your derived class
+		virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+		virtual void OnUpdateUI( wxUpdateUIEvent& event ) { event.Skip(); }
+		virtual void OnLocationExit( wxFocusEvent& event ) { event.Skip(); }
+		virtual void OnSelectConnType( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnFileUpdated( wxFileDirPickerEvent& event ) { event.Skip(); }
+		virtual void OnTestClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnOKClick( wxCommandEvent& event ) { event.Skip(); }
+
+
+	public:
+
+		DIALOG_GIT_REPOSITORY_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Git Repository"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 682,598 ), long style = wxCAPTION|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
+
+		~DIALOG_GIT_REPOSITORY_BASE();
+
+};
+
diff --git a/common/dialogs/git/dialog_git_switch.cpp b/common/dialogs/git/dialog_git_switch.cpp
new file mode 100644
index 0000000000..92e57f5e41
--- /dev/null
+++ b/common/dialogs/git/dialog_git_switch.cpp
@@ -0,0 +1,288 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+#include "dialog_git_switch.h"
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/listctrl.h>
+#include <wx/event.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
+#include <git2.h>
+
+
+DIALOG_GIT_SWITCH::DIALOG_GIT_SWITCH( wxWindow* aParent, git_repository* aRepository ) :
+        DIALOG_SHIM( aParent, wxID_ANY, _( "Git Branch Switch" ), wxDefaultPosition, wxDefaultSize,
+                     wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
+        m_timer( this ), m_repository( aRepository )
+{
+    wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
+
+    // Add explanation text
+    wxStaticText* explanationText =
+            new wxStaticText( this, wxID_ANY, _( "Select or enter a branch name:" ) );
+    sizer->Add( explanationText, 0, wxALL, 10 );
+
+    // Add branch list with three columns
+    m_branchList = new wxListView( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
+                                   wxLC_REPORT | wxLC_SINGLE_SEL );
+    m_branchList->InsertColumn( 0, _( "Branch" ) );
+    m_branchList->InsertColumn( 1, _( "Last Commit" ) );
+    m_branchList->InsertColumn( 2, _( "Last Updated" ) );
+    sizer->Add( m_branchList, 1, wxALL | wxEXPAND, 10 );
+
+    // Add branch name text box
+    m_branchNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
+                                       wxDefaultSize, wxTE_PROCESS_ENTER );
+    sizer->Add( m_branchNameText, 0, wxALL | wxEXPAND, 10 );
+
+    // Add buttons
+    wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
+    m_switchButton = new wxButton( this, wxID_OK, _( "Switch" ) );
+    buttonSizer->AddButton( m_switchButton );
+    m_switchButton->Disable();
+    wxButton* cancelButton = new wxButton( this, wxID_CANCEL, _( "Cancel" ) );
+    buttonSizer->AddButton( cancelButton );
+    buttonSizer->Realize();
+    sizer->Add( buttonSizer, 0, wxALIGN_RIGHT | wxALL, 10 );
+
+    // Bind events
+    Bind( wxEVT_LIST_ITEM_SELECTED, &DIALOG_GIT_SWITCH::OnBranchListSelection, this, m_branchList->GetId() );
+    Bind( wxEVT_LIST_ITEM_ACTIVATED, &DIALOG_GIT_SWITCH::OnBranchListDClick, this, m_branchList->GetId() );
+    Bind( wxEVT_BUTTON, &DIALOG_GIT_SWITCH::OnSwitchButton, this, m_switchButton->GetId() );
+    Bind( wxEVT_BUTTON, &DIALOG_GIT_SWITCH::OnCancelButton, this, cancelButton->GetId() );
+    Bind( wxEVT_TEXT, &DIALOG_GIT_SWITCH::OnTextChanged, this, m_branchNameText->GetId() );
+    Bind( wxEVT_TIMER, &DIALOG_GIT_SWITCH::OnTimer, this, m_timer.GetId() );
+
+    // Populate branch list
+    PopulateBranchList();
+
+    // Set sizer for the dialog
+    SetSizerAndFit( sizer );
+
+    finishDialogSettings();
+
+    m_existingBranch = false;
+}
+
+DIALOG_GIT_SWITCH::~DIALOG_GIT_SWITCH()
+{
+    StopTimer();
+    Unbind( wxEVT_TIMER, &DIALOG_GIT_SWITCH::OnTimer, this, m_timer.GetId() );
+}
+
+void DIALOG_GIT_SWITCH::PopulateBranchList()
+{
+    m_branchList->DeleteAllItems();
+
+    // Get the branches
+    GetBranches();
+
+    // Populate the list
+    for( auto& [ name, data ] : m_branches )
+    {
+        wxDateTime lastUpdated( data.lastUpdated );
+        wxString   lastUpdatedString = lastUpdated.Format();
+
+        long itemIndex = m_branchList->InsertItem( m_branchList->GetItemCount(), name );
+        m_branchList->SetItem( itemIndex, 1, data.commitString );
+        m_branchList->SetItem( itemIndex, 2, lastUpdatedString );
+    }
+
+    m_branchList->SetColumnWidth( 0, wxLIST_AUTOSIZE );
+    m_branchList->SetColumnWidth( 1, wxLIST_AUTOSIZE );
+    m_branchList->SetColumnWidth( 2, wxLIST_AUTOSIZE );
+
+}
+
+
+void DIALOG_GIT_SWITCH::OnBranchListDClick( wxListEvent& aEvent )
+{
+    int selection = aEvent.GetIndex();
+
+    if( selection != wxNOT_FOUND )
+    {
+        wxString branchName = m_branchList->GetItemText( selection );
+        m_branchNameText->SetValue( branchName );
+
+        if( branchName != m_currentBranch )
+            EndModal( wxID_OK );
+    }
+}
+
+
+void DIALOG_GIT_SWITCH::OnBranchListSelection( wxListEvent& aEvent )
+{
+    int selection = aEvent.GetIndex();
+
+    if( selection != wxNOT_FOUND )
+    {
+        wxString branchName = m_branchList->GetItemText( selection );
+        m_branchNameText->SetValue( branchName );
+        m_switchButton->SetLabel( _( "Switch" ) );
+        m_switchButton->Enable( branchName != m_currentBranch );
+    }
+    else
+    {
+        // Deselect all elements in the list
+        for( int ii = 0; ii < m_branchList->GetItemCount(); ++ii )
+            m_branchList->SetItemState( ii, 0, 0 );
+    }
+}
+
+void DIALOG_GIT_SWITCH::OnSwitchButton(wxCommandEvent& aEvent)
+{
+    wxString branchName = m_branchNameText->GetValue();
+
+    // Check if the branch name exists
+    bool branchExists = m_branches.count(branchName);
+
+    if (branchExists)
+    {
+        EndModal(wxID_OK); // Return Switch code
+    }
+    else
+    {
+        EndModal(wxID_ADD); // Return Add code
+    }
+}
+
+
+void DIALOG_GIT_SWITCH::OnCancelButton(wxCommandEvent& aEvent)
+{
+    EndModal(wxID_CANCEL); // Return Cancel code
+}
+
+
+wxString DIALOG_GIT_SWITCH::GetBranchName() const
+{
+    return m_branchNameText->GetValue();
+}
+
+
+void DIALOG_GIT_SWITCH::StartTimer()
+{
+    m_timer.Start( 500, true );
+}
+
+
+void DIALOG_GIT_SWITCH::StopTimer()
+{
+    m_timer.Stop();
+}
+
+
+void DIALOG_GIT_SWITCH::OnTimer( wxTimerEvent& aEvt )
+{
+    wxString branchName = m_branchNameText->GetValue();
+
+    if( branchName == m_lastEnteredText )
+        return;
+
+    m_lastEnteredText = branchName;
+
+    // Check if the branch name exists
+    bool branchExists = m_branches.count( branchName );
+
+    if( branchExists )
+    {
+        m_switchButton->SetLabel( _( "Switch" ) );
+        m_switchButton->Enable( branchName != m_currentBranch );
+    }
+    else
+    {
+        m_switchButton->SetLabel( _( "Add" ) );
+        m_switchButton->Enable();
+    }
+}
+
+
+void DIALOG_GIT_SWITCH::OnTextChanged( wxCommandEvent& aEvt )
+{
+    StartTimer();
+}
+
+
+void DIALOG_GIT_SWITCH::GetBranches()
+{
+    // Clear the branch list
+    m_branches.clear();
+
+    git_branch_iterator* branchIterator = nullptr;
+    git_branch_t         branchType;
+
+    // Get Current Branch
+    git_reference* currentBranchReference = nullptr;
+    git_repository_head( &currentBranchReference, m_repository );
+
+    // Get the current branch name
+    if( currentBranchReference )
+    {
+        m_currentBranch = git_reference_shorthand( currentBranchReference );
+        git_reference_free( currentBranchReference );
+    }
+
+    // Initialize branch iterator
+    git_branch_iterator_new( &branchIterator, m_repository, GIT_BRANCH_ALL );
+
+    // Iterate over local branches
+    git_reference* branchReference = nullptr;
+    while( git_branch_next( &branchReference, &branchType, branchIterator ) == 0 )
+    {
+        // Get the branch OID
+        const git_oid* branchOid = git_reference_target( branchReference );
+
+        // Skip this branch if it doesn't have an OID
+        if( !branchOid )
+        {
+            git_reference_free( branchReference );
+            continue;
+        }
+
+        git_commit* commit = nullptr;
+
+        if( git_commit_lookup( &commit, m_repository, branchOid ) )
+        {
+            // Skip this branch if it doesn't have a commit
+            git_reference_free( branchReference );
+            continue;
+        }
+
+        // Retrieve commit details
+        BranchData branchData;
+        branchData.commitString = git_commit_message( commit );
+        branchData.lastUpdated = static_cast<time_t>( git_commit_time( commit ) );
+        branchData.isRemote = branchType == GIT_BRANCH_REMOTE;
+
+        m_branches[git_reference_shorthand( branchReference )] = branchData;
+
+        git_commit_free( commit );
+        git_reference_free( branchReference );
+    }
+
+    git_branch_iterator_free( branchIterator );
+}
\ No newline at end of file
diff --git a/common/dialogs/git/dialog_git_switch.h b/common/dialogs/git/dialog_git_switch.h
new file mode 100644
index 0000000000..b1dc22a87c
--- /dev/null
+++ b/common/dialogs/git/dialog_git_switch.h
@@ -0,0 +1,79 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+#ifndef DIALOG_GIT_SWITCH_H
+#define DIALOG_GIT_SWITCH_H
+
+#include <dialog_shim.h>
+#include <git2.h>
+
+class wxButton;
+class wxListView;
+class wxTextCtrl;
+class wxTimer;
+class wxListEvent;
+
+struct BranchData
+{
+    wxString commitString;
+    time_t   lastUpdated;
+    bool     isRemote;
+};
+
+class DIALOG_GIT_SWITCH : public DIALOG_SHIM
+{
+public:
+    DIALOG_GIT_SWITCH(wxWindow* aParent, git_repository* aRepository);
+    virtual ~DIALOG_GIT_SWITCH();
+
+    wxString GetBranchName() const;
+
+private:
+    void PopulateBranchList();
+    void OnBranchListSelection(wxListEvent& event);
+    void OnBranchListDClick(wxListEvent& event);
+    void OnSwitchButton(wxCommandEvent& event);
+    void OnCancelButton(wxCommandEvent& event);
+    void OnTextChanged(wxCommandEvent& event);
+    void OnTimer(wxTimerEvent& event);
+    void GetBranches();
+
+    wxListView* m_branchList;
+    wxTextCtrl* m_branchNameText;
+    wxButton* m_switchButton;
+    wxTimer m_timer;
+
+    wxString m_currentBranch;
+
+    git_repository* m_repository;
+    wxString m_lastEnteredText;
+    bool m_existingBranch;
+
+    std::map<wxString, BranchData> m_branches;
+
+    void StartTimer();
+    void StopTimer();
+};
+
+#endif // DIALOG_GIT_SWITCH_H
diff --git a/common/dialogs/git/panel_git_repos.cpp b/common/dialogs/git/panel_git_repos.cpp
new file mode 100644
index 0000000000..2ab0158b9c
--- /dev/null
+++ b/common/dialogs/git/panel_git_repos.cpp
@@ -0,0 +1,377 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "panel_git_repos.h"
+
+#include <bitmaps.h>
+#include <dialogs/git/dialog_git_repository.h>
+#include <kiplatform/secrets.h>
+#include <pgm_base.h>
+#include <settings/common_settings.h>
+#include <widgets/std_bitmap_button.h>
+#include <widgets/wx_grid.h>
+
+#include <git2.h>
+#include <wx/bmpbuttn.h>
+#include <wx/button.h>
+#include <wx/checkbox.h>
+
+
+PANEL_GIT_REPOS::PANEL_GIT_REPOS( wxWindow* aParent ) : PANEL_GIT_REPOS_BASE( aParent)
+{
+
+    m_btnAddRepo->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
+    m_btnEditRepo->SetBitmap( KiBitmap( BITMAPS::small_edit ) );
+    m_btnDelete->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
+
+}
+
+PANEL_GIT_REPOS::~PANEL_GIT_REPOS()
+{
+}
+
+
+void PANEL_GIT_REPOS::ResetPanel()
+{
+    m_grid->ClearGrid();
+    m_cbDefault->SetValue( true );
+    m_author->SetValue( wxEmptyString );
+    m_authorEmail->SetValue( wxEmptyString );
+}
+
+static std::pair<wxString, wxString> getDefaultAuthorEmail()
+{
+    wxString name;
+    wxString email;
+    git_config_entry* name_c = nullptr;
+    git_config_entry* email_c = nullptr;
+
+    git_config* config = nullptr;
+
+    if( git_config_open_default( &config ) != 0 )
+    {
+        printf( "Failed to open default Git config: %s\n", giterr_last()->message );
+        return std::make_pair( name, email );
+    }
+
+    if( git_config_get_entry( &name_c, config, "user.name" ) != 0 )
+    {
+        printf( "Failed to get user.name from Git config: %s\n", giterr_last()->message );
+    }
+    if( git_config_get_entry( &email_c, config, "user.email" ) != 0 )
+    {
+        printf( "Failed to get user.email from Git config: %s\n", giterr_last()->message );
+    }
+
+    if( name_c )
+        name = name_c->value;
+
+    if( email_c )
+        email = email_c->value;
+
+    git_config_entry_free( name_c );
+    git_config_entry_free( email_c );
+    git_config_free( config );
+
+    return std::make_pair( name, email );
+}
+
+bool PANEL_GIT_REPOS::TransferDataFromWindow()
+{
+    COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
+    auto& repos = settings->m_Git.repositories;
+
+    repos.clear();
+
+    for( int row = 0; row < m_grid->GetNumberRows(); row++ )
+    {
+        COMMON_SETTINGS::GIT_REPOSITORY repo;
+
+        repo.active = m_grid->GetCellValue( row, COL_ACTIVE ) == "1";
+        repo.name = m_grid->GetCellValue( row, COL_NAME );
+        repo.path = m_grid->GetCellValue( row, COL_PATH );
+        repo.authType = m_grid->GetCellValue( row, COL_AUTH_TYPE );
+        repo.username = m_grid->GetCellValue( row, COL_USERNAME );
+
+        KIPLATFORM::SECRETS::StoreSecret( repo.path, repo.username, m_grid->GetCellValue( row, COL_PASSWORD ) );
+        repo.ssh_path = m_grid->GetCellValue( row, COL_SSH_PATH );
+        repos.push_back( repo );
+    }
+
+    settings->m_Git.useDefaultAuthor = m_cbDefault->GetValue();
+    settings->m_Git.authorName = m_author->GetValue();
+    settings->m_Git.authorEmail = m_authorEmail->GetValue();
+
+    return true;
+}
+
+static bool testRepositoryConnection( COMMON_SETTINGS::GIT_REPOSITORY& repository)
+{
+    git_libgit2_init();
+
+    git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+    typedef struct
+    {
+        COMMON_SETTINGS::GIT_REPOSITORY* repo;
+        bool success;
+    } callbacksPayload;
+
+    callbacksPayload cb_data( { &repository, true } );  // If we don't need authentication, then, we are successful
+    callbacks.payload = &cb_data;
+    callbacks.credentials = [](git_cred** out, const char* url, const char* username, unsigned int allowed_types, void* payload) -> int {
+
+        // If we are asking for credentials, then, we need authentication
+
+        callbacksPayload* data = static_cast<callbacksPayload*>(payload);
+
+        data->success = false;
+
+        if( allowed_types & GIT_CREDTYPE_USERNAME )
+        {
+            data->success = true;
+        }
+        else if( data->repo->authType == "ssh" && ( allowed_types & GIT_CREDTYPE_SSH_KEY ) )
+        {
+            wxString sshKeyPath = data->repo->ssh_path;
+
+            // Check if the SSH key exists and is readable
+            if( wxFileExists( sshKeyPath ) && wxFile::Access( sshKeyPath, wxFile::read ) )
+                data->success = true;
+        }
+        else if( data->repo->authType == "password" )
+        {
+            data->success = ( allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT );
+        }
+
+        return 0;
+    };
+
+    // Create a temporary directory to initialize the Git repository
+    wxString tempDirPath = wxFileName::CreateTempFileName(wxT("kigit_temp"));
+    wxMkDir(tempDirPath, wxS_DIR_DEFAULT );
+
+    // Initialize the Git repository
+    git_repository* repo = nullptr;
+    int result = git_repository_init(&repo, tempDirPath.mb_str(wxConvUTF8), 0);
+    if (result != 0) {
+        git_repository_free(repo);
+        git_libgit2_shutdown();
+        wxRmdir(tempDirPath);
+        return false;
+    }
+
+    git_remote* remote = nullptr;
+    result = git_remote_create_anonymous(&remote, repo, tempDirPath.mb_str(wxConvUTF8));
+    if (result != 0) {
+        git_remote_free(remote);
+        git_repository_free(repo);
+        git_libgit2_shutdown();
+        wxRmdir(tempDirPath);
+        return false;
+    }
+
+    // We don't really care about the result of this call, the authentication callback
+    // will set the return values we need
+    git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, nullptr, nullptr);
+
+    git_remote_disconnect(remote);
+    git_remote_free(remote);
+    git_repository_free(repo);
+
+    git_libgit2_shutdown();
+
+    // Clean up the temporary directory
+    wxRmdir(tempDirPath);
+
+    return cb_data.success;
+}
+
+bool PANEL_GIT_REPOS::TransferDataToWindow()
+{
+    COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
+
+    m_grid->ClearGrid();
+
+    for( COMMON_SETTINGS::GIT_REPOSITORY& repo : settings->m_Git.repositories )
+    {
+        if( repo.name.IsEmpty() || repo.path.IsEmpty() )
+            continue;
+
+        int row = m_grid->GetNumberRows();
+        m_grid->AppendRows( 1 );
+
+        m_grid->SetCellRenderer( row, COL_ACTIVE, new wxGridCellBoolRenderer() );
+        m_grid->SetCellEditor( row, COL_ACTIVE, new wxGridCellBoolEditor() );
+        m_grid->SetCellValue( row, COL_ACTIVE, repo.active ? "1" : "0" );
+
+        m_grid->SetCellValue( row, COL_NAME, repo.name );
+        m_grid->SetCellValue( row, COL_PATH, repo.path );
+        m_grid->SetCellValue( row, COL_AUTH_TYPE, repo.authType );
+        m_grid->SetCellValue( row, COL_USERNAME, repo.username );
+
+        wxString password;
+        KIPLATFORM::SECRETS::GetSecret( repo.path, repo.username, password );
+        m_grid->SetCellValue( row, COL_PASSWORD, password );
+        m_grid->SetCellValue( row, COL_SSH_PATH, repo.ssh_path );
+
+        if( repo.active )
+            m_grid->SetCellValue( row, 3, testRepositoryConnection( repo ) ? "C" : "NC" );
+
+    }
+
+    m_cbDefault->SetValue( settings->m_Git.useDefaultAuthor );
+
+    if( settings->m_Git.useDefaultAuthor )
+    {
+        auto defaultAuthor = getDefaultAuthorEmail();
+        m_author->SetValue( defaultAuthor.first );
+        m_authorEmail->SetValue( defaultAuthor.second );
+        m_author->Disable();
+        m_authorEmail->Disable();
+    }
+    else
+    {
+        m_author->SetValue( settings->m_Git.authorName );
+        m_authorEmail->SetValue( settings->m_Git.authorEmail );
+    }
+
+    return true;
+}
+
+void PANEL_GIT_REPOS::onDefaultClick( wxCommandEvent& event )
+{
+    m_author->Enable( !m_cbDefault->GetValue() );
+    m_authorEmail->Enable( !m_cbDefault->GetValue() );
+    m_authorLabel->Enable( !m_cbDefault->GetValue() );
+    m_authorEmailLabel->Enable( !m_cbDefault->GetValue() );
+}
+
+
+void PANEL_GIT_REPOS::onGridDClick( wxGridEvent& event )
+{
+    if( m_grid->GetNumberRows() <= 0 )
+    {
+        wxCommandEvent evt;
+        onAddClick( evt );
+        return;
+    }
+
+    int row = event.GetRow();
+
+    if( row < 0 || row >= m_grid->GetNumberRows() )
+        return;
+
+    DIALOG_GIT_REPOSITORY dialog( this, nullptr );
+
+    dialog.SetRepoName( m_grid->GetCellValue( row, COL_NAME ) );
+    dialog.SetRepoURL( m_grid->GetCellValue( row, COL_PATH ) );
+    dialog.SetUsername( m_grid->GetCellValue( row, COL_USERNAME ) );
+    dialog.SetRepoSSHPath( m_grid->GetCellValue( row, COL_SSH_PATH ) );
+    dialog.SetPassword( m_grid->GetCellValue( row, COL_PASSWORD ) );
+
+    wxString type = m_grid->GetCellValue( row, COL_AUTH_TYPE );
+
+    if( type == "password" )
+        dialog.SetRepoType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS );
+    else if( type == "ssh" )
+        dialog.SetRepoType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH );
+    else
+        dialog.SetRepoType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL);
+
+    if( dialog.ShowModal() == wxID_OK )
+    {
+        m_grid->SetCellValue( row, COL_NAME, dialog.GetRepoName() );
+        m_grid->SetCellValue( row, COL_PATH, dialog.GetRepoURL() );
+        m_grid->SetCellValue( row, COL_USERNAME, dialog.GetUsername() );
+        m_grid->SetCellValue( row, COL_SSH_PATH, dialog.GetRepoSSHPath() );
+        m_grid->SetCellValue( row, COL_PASSWORD, dialog.GetPassword() );
+
+        if( dialog.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "password" );
+        }
+        else if( dialog.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "ssh" );
+        }
+        else
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "none" );
+        }
+    }
+
+}
+
+
+void PANEL_GIT_REPOS::onAddClick( wxCommandEvent& event )
+{
+
+    DIALOG_GIT_REPOSITORY dialog( m_parent, nullptr );
+
+    if( dialog.ShowModal() == wxID_OK )
+    {
+        int row = m_grid->GetNumberRows();
+        m_grid->AppendRows( 1 );
+
+        m_grid->SetCellValue( row, COL_NAME, dialog.GetRepoName() );
+        m_grid->SetCellValue( row, COL_PATH, dialog.GetRepoURL() );
+        m_grid->SetCellValue( row, COL_USERNAME, dialog.GetUsername() );
+        m_grid->SetCellValue( row, COL_SSH_PATH, dialog.GetRepoSSHPath() );
+        m_grid->SetCellValue( row, COL_PASSWORD, dialog.GetPassword() );
+
+        if( dialog.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "password" );
+        }
+        else if( dialog.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "ssh" );
+        }
+        else
+        {
+            m_grid->SetCellValue( row, COL_AUTH_TYPE, "none" );
+        }
+
+        m_grid->MakeCellVisible( row, 0 );
+    }
+}
+
+
+void PANEL_GIT_REPOS::onEditClick( wxCommandEvent& event )
+{
+    wxGridEvent evt( m_grid->GetId(), wxEVT_GRID_CELL_LEFT_DCLICK, m_grid,
+                     m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
+    onGridDClick( evt );
+}
+
+
+void PANEL_GIT_REPOS::onDeleteClick( wxCommandEvent& event )
+{
+    if( !m_grid->CommitPendingChanges() || m_grid->GetNumberRows() <= 0 )
+        return;
+
+    int curRow   = m_grid->GetGridCursorRow();
+
+    m_grid->DeleteRows( curRow );
+
+    curRow = std::max( 0, curRow - 1 );
+    m_grid->MakeCellVisible( curRow, m_grid->GetGridCursorCol() );
+    m_grid->SetGridCursor( curRow, m_grid->GetGridCursorCol() );
+}
\ No newline at end of file
diff --git a/common/dialogs/git/panel_git_repos.h b/common/dialogs/git/panel_git_repos.h
new file mode 100644
index 0000000000..7a2501f296
--- /dev/null
+++ b/common/dialogs/git/panel_git_repos.h
@@ -0,0 +1,59 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PANEL_GIT_REPOS_H
+#define PANEL_GIT_REPOS_H
+
+#include <git/panel_git_repos_base.h>
+#include <widgets/wx_grid.h>
+
+class PANEL_GIT_REPOS : public PANEL_GIT_REPOS_BASE
+{
+public:
+    PANEL_GIT_REPOS( wxWindow* parent );
+    ~PANEL_GIT_REPOS() override;
+
+    void ResetPanel() override;
+
+    bool TransferDataFromWindow() override;
+    bool TransferDataToWindow() override;
+
+    enum COLS
+    {
+        COL_ACTIVE = 0,
+        COL_NAME,
+        COL_PATH,
+        COL_STATUS,
+        COL_AUTH_TYPE,
+        COL_USERNAME,
+        COL_PASSWORD,
+        COL_SSH_KEY,
+        COL_SSH_PATH
+    };
+
+private:
+    void onDefaultClick( wxCommandEvent& event ) override;
+    void onGridDClick( wxGridEvent& event ) override;
+    void onAddClick( wxCommandEvent& event ) override;
+    void onEditClick( wxCommandEvent& event ) override;
+    void onDeleteClick( wxCommandEvent& event ) override;
+
+};
+
+#endif // PANEL_GIT_REPOS_H
\ No newline at end of file
diff --git a/common/dialogs/git/panel_git_repos_base.cpp b/common/dialogs/git/panel_git_repos_base.cpp
new file mode 100644
index 0000000000..0640698ef7
--- /dev/null
+++ b/common/dialogs/git/panel_git_repos_base.cpp
@@ -0,0 +1,167 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-254-gc2ef7767)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "widgets/std_bitmap_button.h"
+#include "widgets/wx_grid.h"
+
+#include "panel_git_repos_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+PANEL_GIT_REPOS_BASE::PANEL_GIT_REPOS_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : RESETTABLE_PANEL( parent, id, pos, size, style, name )
+{
+	wxBoxSizer* bPanelSizer;
+	bPanelSizer = new wxBoxSizer( wxHORIZONTAL );
+
+	wxBoxSizer* bLeftSizer;
+	bLeftSizer = new wxBoxSizer( wxVERTICAL );
+
+	m_staticText12 = new wxStaticText( this, wxID_ANY, _("Git Commit Data"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText12->Wrap( -1 );
+	bLeftSizer->Add( m_staticText12, 0, wxEXPAND|wxLEFT|wxTOP, 10 );
+
+	wxFlexGridSizer* fgSizer1;
+	fgSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 );
+	fgSizer1->AddGrowableCol( 1 );
+	fgSizer1->SetFlexibleDirection( wxBOTH );
+	fgSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
+
+	m_cbDefault = new wxCheckBox( this, wxID_ANY, _("Use default values"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_cbDefault->SetValue(true);
+	fgSizer1->Add( m_cbDefault, 0, wxALL, 5 );
+
+
+	fgSizer1->Add( 0, 0, 0, wxEXPAND, 5 );
+
+	m_authorLabel = new wxStaticText( this, wxID_ANY, _("Author name:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_authorLabel->Wrap( -1 );
+	m_authorLabel->Enable( false );
+
+	fgSizer1->Add( m_authorLabel, 0, wxALL, 5 );
+
+	m_author = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	m_author->Enable( false );
+
+	fgSizer1->Add( m_author, 0, wxALL|wxEXPAND, 5 );
+
+	m_authorEmailLabel = new wxStaticText( this, wxID_ANY, _("Author e-mail:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_authorEmailLabel->Wrap( -1 );
+	m_authorEmailLabel->Enable( false );
+
+	fgSizer1->Add( m_authorEmailLabel, 0, wxALL, 5 );
+
+	m_authorEmail = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	m_authorEmail->Enable( false );
+
+	fgSizer1->Add( m_authorEmail, 0, wxALL|wxEXPAND, 5 );
+
+
+	bLeftSizer->Add( fgSizer1, 1, wxBOTTOM|wxEXPAND|wxLEFT|wxTOP, 13 );
+
+	m_staticline3 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+	bLeftSizer->Add( m_staticline3, 0, wxEXPAND|wxBOTTOM, 5 );
+
+	m_staticText20 = new wxStaticText( this, wxID_ANY, _("Git Repositories"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText20->Wrap( -1 );
+	bLeftSizer->Add( m_staticText20, 0, wxEXPAND|wxLEFT|wxRIGHT, 13 );
+
+	wxBoxSizer* bAntialiasingSizer;
+	bAntialiasingSizer = new wxBoxSizer( wxVERTICAL );
+
+	m_grid = new WX_GRID( this, wxID_ANY, wxDefaultPosition, wxSize( 820,200 ), 0 );
+
+	// Grid
+	m_grid->CreateGrid( 0, 10 );
+	m_grid->EnableEditing( false );
+	m_grid->EnableGridLines( true );
+	m_grid->EnableDragGridSize( false );
+	m_grid->SetMargins( 0, 0 );
+
+	// Columns
+	m_grid->SetColSize( 0, 60 );
+	m_grid->SetColSize( 1, 200 );
+	m_grid->SetColSize( 2, 500 );
+	m_grid->SetColSize( 3, 60 );
+	m_grid->SetColSize( 4, 0 );
+	m_grid->SetColSize( 5, 0 );
+	m_grid->SetColSize( 6, 0 );
+	m_grid->SetColSize( 7, 0 );
+	m_grid->SetColSize( 8, 0 );
+	m_grid->SetColSize( 9, 0 );
+	m_grid->EnableDragColMove( false );
+	m_grid->EnableDragColSize( true );
+	m_grid->SetColLabelValue( 0, _("Active") );
+	m_grid->SetColLabelValue( 1, _("Name") );
+	m_grid->SetColLabelValue( 2, _("Path") );
+	m_grid->SetColLabelValue( 3, _("Status") );
+	m_grid->SetColLabelSize( 22 );
+	m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Rows
+	m_grid->EnableDragRowSize( true );
+	m_grid->SetRowLabelSize( 0 );
+	m_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Label Appearance
+
+	// Cell Defaults
+	m_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_TOP );
+	bAntialiasingSizer->Add( m_grid, 5, wxALL|wxEXPAND, 5 );
+
+
+	bLeftSizer->Add( bAntialiasingSizer, 0, wxEXPAND|wxLEFT|wxTOP, 5 );
+
+	wxBoxSizer* bButtonsSizer;
+	bButtonsSizer = new wxBoxSizer( wxHORIZONTAL );
+
+	m_btnAddRepo = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
+	m_btnAddRepo->SetToolTip( _("Add new repository") );
+
+	bButtonsSizer->Add( m_btnAddRepo, 0, wxALL, 5 );
+
+	m_btnEditRepo = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
+	m_btnEditRepo->SetToolTip( _("Edit repository properties") );
+
+	bButtonsSizer->Add( m_btnEditRepo, 0, wxALL, 5 );
+
+
+	bButtonsSizer->Add( 0, 0, 1, wxEXPAND, 5 );
+
+	m_btnDelete = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
+	m_btnDelete->SetToolTip( _("Remove Git Repository") );
+
+	bButtonsSizer->Add( m_btnDelete, 0, wxBOTTOM|wxRIGHT|wxTOP, 5 );
+
+
+	bLeftSizer->Add( bButtonsSizer, 1, wxALL|wxEXPAND, 5 );
+
+
+	bPanelSizer->Add( bLeftSizer, 0, wxRIGHT, 20 );
+
+
+	this->SetSizer( bPanelSizer );
+	this->Layout();
+	bPanelSizer->Fit( this );
+
+	// Connect Events
+	m_cbDefault->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onDefaultClick ), NULL, this );
+	m_grid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( PANEL_GIT_REPOS_BASE::onGridDClick ), NULL, this );
+	m_btnAddRepo->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onAddClick ), NULL, this );
+	m_btnEditRepo->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onEditClick ), NULL, this );
+	m_btnDelete->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onDeleteClick ), NULL, this );
+}
+
+PANEL_GIT_REPOS_BASE::~PANEL_GIT_REPOS_BASE()
+{
+	// Disconnect Events
+	m_cbDefault->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onDefaultClick ), NULL, this );
+	m_grid->Disconnect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( PANEL_GIT_REPOS_BASE::onGridDClick ), NULL, this );
+	m_btnAddRepo->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onAddClick ), NULL, this );
+	m_btnEditRepo->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onEditClick ), NULL, this );
+	m_btnDelete->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_GIT_REPOS_BASE::onDeleteClick ), NULL, this );
+
+}
diff --git a/common/dialogs/git/panel_git_repos_base.h b/common/dialogs/git/panel_git_repos_base.h
new file mode 100644
index 0000000000..c6001fdac3
--- /dev/null
+++ b/common/dialogs/git/panel_git_repos_base.h
@@ -0,0 +1,73 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-254-gc2ef7767)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/intl.h>
+class STD_BITMAP_BUTTON;
+class WX_GRID;
+
+#include "widgets/resettable_panel.h"
+#include <wx/string.h>
+#include <wx/stattext.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/checkbox.h>
+#include <wx/textctrl.h>
+#include <wx/sizer.h>
+#include <wx/statline.h>
+#include <wx/grid.h>
+#include <wx/bmpbuttn.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/button.h>
+#include <wx/panel.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class PANEL_GIT_REPOS_BASE
+///////////////////////////////////////////////////////////////////////////////
+class PANEL_GIT_REPOS_BASE : public RESETTABLE_PANEL
+{
+	private:
+
+	protected:
+		wxStaticText* m_staticText12;
+		wxCheckBox* m_cbDefault;
+		wxStaticText* m_authorLabel;
+		wxTextCtrl* m_author;
+		wxStaticText* m_authorEmailLabel;
+		wxTextCtrl* m_authorEmail;
+		wxStaticLine* m_staticline3;
+		wxStaticText* m_staticText20;
+		WX_GRID* m_grid;
+		STD_BITMAP_BUTTON* m_btnAddRepo;
+		STD_BITMAP_BUTTON* m_btnEditRepo;
+		STD_BITMAP_BUTTON* m_btnDelete;
+
+		// Virtual event handlers, override them in your derived class
+		virtual void onDefaultClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onGridDClick( wxGridEvent& event ) { event.Skip(); }
+		virtual void onAddClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onEditClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onDeleteClick( wxCommandEvent& event ) { event.Skip(); }
+
+
+	public:
+
+		PANEL_GIT_REPOS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
+
+		~PANEL_GIT_REPOS_BASE();
+
+};
+
diff --git a/common/dialogs/git/panel_git_repositories_base.fbp b/common/dialogs/git/panel_git_repositories_base.fbp
new file mode 100644
index 0000000000..da03f75ab2
--- /dev/null
+++ b/common/dialogs/git/panel_git_repositories_base.fbp
@@ -0,0 +1,953 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="16" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">panel_git_repos_base</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="image_path_wrapper_function_name"></property>
+        <property name="indent_with_spaces"></property>
+        <property name="internationalize">1</property>
+        <property name="name">PanelGitRepos</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_array_enum">0</property>
+        <property name="use_enum">1</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Panel" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="drag_accept_files">0</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">PANEL_GIT_REPOS_BASE</property>
+            <property name="pos"></property>
+            <property name="size">-1,-1</property>
+            <property name="subclass">RESETTABLE_PANEL; widgets/resettable_panel.h; Not forward_declare</property>
+            <property name="tooltip"></property>
+            <property name="two_step_creation">0</property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style">wxTAB_TRAVERSAL</property>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bPanelSizer</property>
+                <property name="orient">wxHORIZONTAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">20</property>
+                    <property name="flag">wxRIGHT</property>
+                    <property name="proportion">0</property>
+                    <object class="wxBoxSizer" expanded="1">
+                        <property name="minimum_size"></property>
+                        <property name="name">bLeftSizer</property>
+                        <property name="orient">wxVERTICAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">10</property>
+                            <property name="flag">wxEXPAND|wxLEFT|wxTOP</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticText" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Git Commit Data</property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticText12</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <property name="wrap">-1</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">13</property>
+                            <property name="flag">wxBOTTOM|wxEXPAND|wxLEFT|wxTOP</property>
+                            <property name="proportion">1</property>
+                            <object class="wxFlexGridSizer" expanded="1">
+                                <property name="cols">2</property>
+                                <property name="flexible_direction">wxBOTH</property>
+                                <property name="growablecols">1</property>
+                                <property name="growablerows"></property>
+                                <property name="hgap">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="name">fgSizer1</property>
+                                <property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
+                                <property name="permission">none</property>
+                                <property name="rows">0</property>
+                                <property name="vgap">0</property>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxCheckBox" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="checked">1</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">Use default values</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_cbDefault</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnCheckBox">onDefaultClick</event>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxEXPAND</property>
+                                    <property name="proportion">0</property>
+                                    <object class="spacer" expanded="0">
+                                        <property name="height">0</property>
+                                        <property name="permission">protected</property>
+                                        <property name="width">0</property>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxStaticText" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">0</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">Author name:</property>
+                                        <property name="markup">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_authorLabel</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <property name="wrap">-1</property>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL|wxEXPAND</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxTextCtrl" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">0</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="maxlength"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_author</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="value"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxStaticText" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">0</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">Author e-mail:</property>
+                                        <property name="markup">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_authorEmailLabel</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <property name="wrap">-1</property>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL|wxEXPAND</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxTextCtrl" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">0</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="maxlength"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_authorEmail</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">; ; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="value"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND|wxBOTTOM</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticLine" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticline3</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style">wxLI_HORIZONTAL</property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">13</property>
+                            <property name="flag">wxEXPAND|wxLEFT|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStaticText" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_accept_files">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Git Repositories</property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_staticText20</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <property name="wrap">-1</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND|wxLEFT|wxTOP</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBoxSizer" expanded="1">
+                                <property name="minimum_size"></property>
+                                <property name="name">bAntialiasingSizer</property>
+                                <property name="orient">wxVERTICAL</property>
+                                <property name="permission">none</property>
+                                <object class="sizeritem" expanded="0">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL|wxEXPAND</property>
+                                    <property name="proportion">5</property>
+                                    <object class="wxGrid" expanded="0">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="autosize_cols">0</property>
+                                        <property name="autosize_rows">0</property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="cell_bg"></property>
+                                        <property name="cell_font"></property>
+                                        <property name="cell_horiz_alignment">wxALIGN_LEFT</property>
+                                        <property name="cell_text"></property>
+                                        <property name="cell_vert_alignment">wxALIGN_TOP</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="col_label_horiz_alignment">wxALIGN_CENTER</property>
+                                        <property name="col_label_size">22</property>
+                                        <property name="col_label_values">&quot;Active&quot; &quot;Name&quot; &quot;Path&quot; &quot;Status&quot;</property>
+                                        <property name="col_label_vert_alignment">wxALIGN_CENTER</property>
+                                        <property name="cols">10</property>
+                                        <property name="column_sizes">60,200,500,60,0,0,0,0,0,0</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="drag_col_move">0</property>
+                                        <property name="drag_col_size">1</property>
+                                        <property name="drag_grid_size">0</property>
+                                        <property name="drag_row_size">1</property>
+                                        <property name="editing">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="font"></property>
+                                        <property name="grid_line_color"></property>
+                                        <property name="grid_lines">1</property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label_bg"></property>
+                                        <property name="label_font"></property>
+                                        <property name="label_text"></property>
+                                        <property name="margin_height">0</property>
+                                        <property name="margin_width">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_grid</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="row_label_horiz_alignment">wxALIGN_CENTER</property>
+                                        <property name="row_label_size">0</property>
+                                        <property name="row_label_values"></property>
+                                        <property name="row_label_vert_alignment">wxALIGN_CENTER</property>
+                                        <property name="row_sizes"></property>
+                                        <property name="rows">0</property>
+                                        <property name="show">1</property>
+                                        <property name="size">820,200</property>
+                                        <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnGridCellLeftDClick">onGridDClick</event>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL|wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxBoxSizer" expanded="1">
+                                <property name="minimum_size"></property>
+                                <property name="name">bButtonsSizer</property>
+                                <property name="orient">wxHORIZONTAL</property>
+                                <property name="permission">none</property>
+                                <object class="sizeritem" expanded="1">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxBitmapButton" expanded="1">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="auth_needed">0</property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="bitmap"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="current"></property>
+                                        <property name="default">0</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="disabled"></property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="focus"></property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">MyButton</property>
+                                        <property name="margins"></property>
+                                        <property name="markup">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_btnAddRepo</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="position"></property>
+                                        <property name="pressed"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip">Add new repository</property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnButtonClick">onAddClick</event>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="1">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxALL</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxBitmapButton" expanded="1">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="auth_needed">0</property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="bitmap"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="current"></property>
+                                        <property name="default">0</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="disabled"></property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="focus"></property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">MyButton</property>
+                                        <property name="margins"></property>
+                                        <property name="markup">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_btnEditRepo</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="position"></property>
+                                        <property name="pressed"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip">Edit repository properties</property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnButtonClick">onEditClick</event>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="1">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxEXPAND</property>
+                                    <property name="proportion">1</property>
+                                    <object class="spacer" expanded="1">
+                                        <property name="height">0</property>
+                                        <property name="permission">protected</property>
+                                        <property name="width">0</property>
+                                    </object>
+                                </object>
+                                <object class="sizeritem" expanded="1">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxBOTTOM|wxRIGHT|wxTOP</property>
+                                    <property name="proportion">0</property>
+                                    <object class="wxBitmapButton" expanded="1">
+                                        <property name="BottomDockable">1</property>
+                                        <property name="LeftDockable">1</property>
+                                        <property name="RightDockable">1</property>
+                                        <property name="TopDockable">1</property>
+                                        <property name="aui_layer"></property>
+                                        <property name="aui_name"></property>
+                                        <property name="aui_position"></property>
+                                        <property name="aui_row"></property>
+                                        <property name="auth_needed">0</property>
+                                        <property name="best_size"></property>
+                                        <property name="bg"></property>
+                                        <property name="bitmap"></property>
+                                        <property name="caption"></property>
+                                        <property name="caption_visible">1</property>
+                                        <property name="center_pane">0</property>
+                                        <property name="close_button">1</property>
+                                        <property name="context_help"></property>
+                                        <property name="context_menu">1</property>
+                                        <property name="current"></property>
+                                        <property name="default">0</property>
+                                        <property name="default_pane">0</property>
+                                        <property name="disabled"></property>
+                                        <property name="dock">Dock</property>
+                                        <property name="dock_fixed">0</property>
+                                        <property name="docking">Left</property>
+                                        <property name="drag_accept_files">0</property>
+                                        <property name="enabled">1</property>
+                                        <property name="fg"></property>
+                                        <property name="floatable">1</property>
+                                        <property name="focus"></property>
+                                        <property name="font"></property>
+                                        <property name="gripper">0</property>
+                                        <property name="hidden">0</property>
+                                        <property name="id">wxID_ANY</property>
+                                        <property name="label">MyButton</property>
+                                        <property name="margins"></property>
+                                        <property name="markup">0</property>
+                                        <property name="max_size"></property>
+                                        <property name="maximize_button">0</property>
+                                        <property name="maximum_size"></property>
+                                        <property name="min_size"></property>
+                                        <property name="minimize_button">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="moveable">1</property>
+                                        <property name="name">m_btnDelete</property>
+                                        <property name="pane_border">1</property>
+                                        <property name="pane_position"></property>
+                                        <property name="pane_size"></property>
+                                        <property name="permission">protected</property>
+                                        <property name="pin_button">1</property>
+                                        <property name="pos"></property>
+                                        <property name="position"></property>
+                                        <property name="pressed"></property>
+                                        <property name="resize">Resizable</property>
+                                        <property name="show">1</property>
+                                        <property name="size"></property>
+                                        <property name="style"></property>
+                                        <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                        <property name="toolbar_pane">0</property>
+                                        <property name="tooltip">Remove Git Repository</property>
+                                        <property name="validator_data_type"></property>
+                                        <property name="validator_style">wxFILTER_NONE</property>
+                                        <property name="validator_type">wxDefaultValidator</property>
+                                        <property name="validator_variable"></property>
+                                        <property name="window_extra_style"></property>
+                                        <property name="window_name"></property>
+                                        <property name="window_style"></property>
+                                        <event name="OnButtonClick">onDeleteClick</event>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp
index ff9443f155..37212b08d2 100644
--- a/common/eda_base_frame.cpp
+++ b/common/eda_base_frame.cpp
@@ -23,9 +23,11 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include <advanced_config.h>
 #include <bitmaps.h>
 #include <bitmap_store.h>
 #include <dialog_shim.h>
+#include <dialogs/git/panel_git_repos.h>
 #include <dialogs/panel_common_settings.h>
 #include <dialogs/panel_mouse_settings.h>
 #include <dialogs/panel_data_collection.h>
@@ -1071,6 +1073,15 @@ void EDA_BASE_FRAME::ShowPreferences( wxString aStartPage, wxString aStartParent
 
     book->AddPage( hotkeysPanel, _( "Hotkeys" ) );
 
+// This currently allows pre-defined repositories that we
+// don't use, so keep it disabled at the moment
+if( ADVANCED_CFG::GetCfg().m_EnableGit && false )
+    book->AddLazyPage(
+            []( wxWindow* aParent ) -> wxWindow*
+            {
+                return new PANEL_GIT_REPOS( aParent );
+            }, _( "Version Control" ) );
+
 #ifdef KICAD_USE_SENTRY
     book->AddLazyPage(
             []( wxWindow* aParent ) -> wxWindow*
diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp
index b7519a6358..5c2149f2b2 100644
--- a/common/eda_shape.cpp
+++ b/common/eda_shape.cpp
@@ -1721,6 +1721,129 @@ PLOT_DASH_TYPE EDA_SHAPE::GetLineStyle() const
 }
 
 
+bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
+{
+    if( GetShape() != aOther.GetShape() )
+        return false;
+
+    if( m_fill != aOther.m_fill )
+        return false;
+
+    if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
+        return false;
+
+    if( m_stroke.GetPlotStyle() != aOther.m_stroke.GetPlotStyle() )
+        return false;
+
+    if( m_fillColor != aOther.m_fillColor )
+        return false;
+
+    if( m_start != aOther.m_start )
+        return false;
+
+    if( m_end != aOther.m_end )
+        return false;
+
+    if( m_arcCenter != aOther.m_arcCenter )
+        return false;
+
+    if( m_bezierC1 != aOther.m_bezierC1 )
+        return false;
+
+    if( m_bezierC2 != aOther.m_bezierC2 )
+        return false;
+
+    if( m_bezierPoints != aOther.m_bezierPoints )
+        return false;
+
+    for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
+    {
+        if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
+{
+    if( GetShape() != aOther.GetShape() )
+        return 0.0;
+
+    double similarity = 1.0;
+
+    if( m_fill != aOther.m_fill )
+        similarity *= 0.9;
+
+    if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
+        similarity *= 0.9;
+
+    if( m_stroke.GetPlotStyle() != aOther.m_stroke.GetPlotStyle() )
+        similarity *= 0.9;
+
+    if( m_fillColor != aOther.m_fillColor )
+        similarity *= 0.9;
+
+    if( m_start != aOther.m_start )
+        similarity *= 0.9;
+
+    if( m_end != aOther.m_end )
+        similarity *= 0.9;
+
+    if( m_arcCenter != aOther.m_arcCenter )
+        similarity *= 0.9;
+
+    if( m_bezierC1 != aOther.m_bezierC1 )
+        similarity *= 0.9;
+
+    if( m_bezierC2 != aOther.m_bezierC2 )
+        similarity *= 0.9;
+
+    {
+        int m = m_bezierPoints.size();
+        int n = aOther.m_bezierPoints.size();
+
+        size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
+
+        similarity *= std::pow( 0.9, m + n - 2 * longest );
+    }
+
+    {
+        int m = m_poly.TotalVertices();
+        int n = aOther.m_poly.TotalVertices();
+        std::vector<VECTOR2I> poly;
+        std::vector<VECTOR2I> otherPoly;
+        VECTOR2I              lastPt( 0, 0 );
+
+        // We look for the longest common subset of the two polygons, but we need to
+        // offset each point because we're actually looking for overall similarity, not just
+        // exact matches.  So if the zone is moved by 1IU, we only want one point to be
+        // considered "moved" rather than the entire polygon.  In this case, the first point
+        // will not be a match but the rest of the sequence will.
+        for( int ii = 0; ii < m; ++ii )
+        {
+            poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
+            lastPt = m_poly.CVertex( ii );
+        }
+
+        lastPt = VECTOR2I( 0, 0 );
+
+        for( int ii = 0; ii < n; ++ii )
+        {
+            otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
+            lastPt = aOther.m_poly.CVertex( ii );
+        }
+
+        size_t longest = alg::longest_common_subset( poly, otherPoly );
+
+        similarity *= std::pow( 0.9, m + n - 2 * longest );
+    }
+
+    return similarity;
+}
+
+
 IMPLEMENT_ENUM_TO_WXANY( SHAPE_T )
 IMPLEMENT_ENUM_TO_WXANY( PLOT_DASH_TYPE )
 
diff --git a/common/eda_text.cpp b/common/eda_text.cpp
index f1d3be7d7b..34df1ed6c7 100644
--- a/common/eda_text.cpp
+++ b/common/eda_text.cpp
@@ -1025,6 +1025,67 @@ bool EDA_TEXT::ValidateHyperlink( const wxString& aURL )
     return false;
 }
 
+double EDA_TEXT::Levenshtein( const EDA_TEXT& aOther ) const
+{
+    // Compute the Levenshtein distance between the two strings
+    const wxString& str1 = GetText();
+    const wxString& str2 = aOther.GetText();
+
+    int m = str1.length();
+    int n = str2.length();
+
+    if( n == 0 || m == 0 )
+        return 0.0;
+
+    // Create a matrix to store the distance values
+    std::vector<std::vector<int>> distance(m + 1, std::vector<int>(n + 1));
+
+    // Initialize the matrix
+    for( int i = 0; i <= m; i++ )
+        distance[i][0] = i;
+    for( int j = 0; j <= n; j++ )
+        distance[0][j] = j;
+
+    // Calculate the distance
+    for( int i = 1; i <= m; i++ )
+    {
+        for( int j = 1; j <= n; j++ )
+        {
+            if( str1[i - 1] == str2[j - 1] )
+            {
+                distance[i][j] = distance[i - 1][j - 1];
+            }
+            else
+            {
+                distance[i][j] = std::min( { distance[i - 1][j], distance[i][j - 1],
+                                             distance[i - 1][j - 1] } ) + 1;
+            }
+        }
+    }
+
+    // Calculate similarity score
+    int    maxLen = std::max( m, n );
+    double similarity = 1.0 - ( static_cast<double>( distance[m][n] ) / maxLen );
+
+    return similarity;
+}
+
+
+double EDA_TEXT::Similarity( const EDA_TEXT& aOther ) const
+{
+    double retval = 1.0;
+
+    if( !( m_attributes == aOther.m_attributes ) )
+        retval *= 0.9;
+
+    if( m_pos != aOther.m_pos )
+        retval *= 0.9;
+
+    retval *= Levenshtein( aOther );
+
+    return retval;
+}
+
 
 bool EDA_TEXT::IsGotoPageHref( const wxString& aHref, wxString* aDestination )
 {
diff --git a/common/gestfich.cpp b/common/gestfich.cpp
index 6861845d4e..4dfec20206 100644
--- a/common/gestfich.cpp
+++ b/common/gestfich.cpp
@@ -40,6 +40,8 @@
 #include <launch_ext.h>
 #include "wx/tokenzr.h"
 
+#include <filesystem>
+
 void QuoteString( wxString& string )
 {
     if( !string.StartsWith( wxT( "\"" ) ) )
@@ -292,3 +294,51 @@ wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
 {
     return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );
 }
+
+
+bool RmDirRecursive( const wxString& aFileName, wxString* aErrors )
+{
+    namespace fs = std::filesystem;
+
+    std::string rmDir = aFileName.ToStdString();
+
+    if( rmDir.length() < 3 )
+    {
+        if( aErrors )
+            *aErrors = _( "Invalid directory name, cannot remove root" );
+
+        return false;
+    }
+
+    if( !fs::exists( rmDir ) )
+    {
+        if( aErrors )
+            *aErrors = wxString::Format( _( "Directory '%s' does not exist" ), aFileName );
+
+        return false;
+    }
+
+    fs::path path( rmDir );
+
+    if( !fs::is_directory( path ) )
+    {
+        if( aErrors )
+            *aErrors = wxString::Format( _( "'%s' is not a directory" ), aFileName );
+
+        return false;
+    }
+
+    try
+    {
+        fs::remove_all( path );
+    }
+    catch( const fs::filesystem_error& e )
+    {
+        if( aErrors )
+            *aErrors = wxString::Format( _( "Error removing directory '%s': %s" ), aFileName, e.what() );
+
+        return false;
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/common/git/git_add_to_index_handler.cpp b/common/git/git_add_to_index_handler.cpp
new file mode 100644
index 0000000000..daf44758b5
--- /dev/null
+++ b/common/git/git_add_to_index_handler.cpp
@@ -0,0 +1,108 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_add_to_index_handler.h"
+
+
+#include <wx/string.h>
+#include <wx/log.h>
+
+GIT_ADD_TO_INDEX_HANDLER::GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository )
+{
+    m_repository = aRepository;
+    m_filesToAdd.clear();
+}
+
+GIT_ADD_TO_INDEX_HANDLER::~GIT_ADD_TO_INDEX_HANDLER()
+{
+}
+
+
+bool GIT_ADD_TO_INDEX_HANDLER::AddToIndex( const wxString& aFilePath )
+{
+    // Test if file is currently in the index
+
+    git_index* index = nullptr;
+    size_t at_pos = 0;
+
+    if( git_repository_index( &index, m_repository ) != 0 )
+    {
+        wxLogError( "Failed to get repository index" );
+        return false;
+    }
+
+    git_index_find( &at_pos, index, aFilePath.ToUTF8().data() );
+
+    if( at_pos >= 0)
+    {
+        git_index_free( index );
+        wxLogError( "%s already in index", aFilePath );
+        return false;
+    }
+
+    git_index_free( index );
+
+    // Add file to index if not already there
+    m_filesToAdd.push_back( aFilePath );
+
+    return true;
+}
+
+
+bool GIT_ADD_TO_INDEX_HANDLER::PerformAddToIndex()
+{
+    git_index* index = nullptr;
+
+    m_filesFailedToAdd.clear();
+
+    if( git_repository_index( &index, m_repository ) != 0 )
+    {
+        wxLogError( "Failed to get repository index" );
+        std::copy( m_filesToAdd.begin(), m_filesToAdd.end(), std::back_inserter( m_filesFailedToAdd ) );
+        return false;
+    }
+
+    for( auto& file : m_filesToAdd )
+    {
+        if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 )
+        {
+            wxLogError( "Failed to add %s to index", file );
+            m_filesFailedToAdd.push_back( file );
+            continue;
+        }
+    }
+
+
+    if( git_index_write( index ) != 0 )
+    {
+        wxLogError( "Failed to write index" );
+        m_filesFailedToAdd.clear();
+        std::copy( m_filesToAdd.begin(), m_filesToAdd.end(), std::back_inserter( m_filesFailedToAdd ) );
+        git_index_free( index );
+        return false;
+    }
+
+    git_index_free( index );
+
+    return true;
+}
\ No newline at end of file
diff --git a/common/git/git_add_to_index_handler.h b/common/git/git_add_to_index_handler.h
new file mode 100644
index 0000000000..68c0983d2a
--- /dev/null
+++ b/common/git/git_add_to_index_handler.h
@@ -0,0 +1,50 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_ADD_TO_INDEX_HANDLER_H_
+#define GIT_ADD_TO_INDEX_HANDLER_H_
+
+#include <git2.h>
+#include <vector>
+
+class wxString;
+
+class GIT_ADD_TO_INDEX_HANDLER
+{
+public:
+    GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository );
+    virtual ~GIT_ADD_TO_INDEX_HANDLER();
+
+    bool AddToIndex( const wxString& aFilePath );
+
+    bool PerformAddToIndex();
+
+private:
+
+        git_repository* m_repository;
+
+        std::vector<wxString> m_filesToAdd;
+        std::vector<wxString> m_filesFailedToAdd;
+    };
+
+#endif /* GIT_ADD_TO_INDEX_HANDLER_H_ */
\ No newline at end of file
diff --git a/common/git/git_clone_handler.cpp b/common/git/git_clone_handler.cpp
new file mode 100644
index 0000000000..a466b69181
--- /dev/null
+++ b/common/git/git_clone_handler.cpp
@@ -0,0 +1,82 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_clone_handler.h"
+
+#include <git/kicad_git_common.h>
+
+#include <git2.h>
+#include <wx/filename.h>
+
+GIT_CLONE_HANDLER::GIT_CLONE_HANDLER() :  KIGIT_COMMON( nullptr )
+{}
+
+GIT_CLONE_HANDLER::~GIT_CLONE_HANDLER()
+{
+    if( m_repo )
+        git_repository_free( m_repo );
+}
+
+
+bool GIT_CLONE_HANDLER::PerformClone()
+{
+    wxFileName clonePath( m_clonePath );
+
+    if( !clonePath.DirExists() )
+    {
+        if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
+        {
+            AddErrorString( wxString::Format( _( "Could not create directory '%s'" ), m_clonePath ) );
+            return false;
+        }
+    }
+
+    git_clone_options cloneOptions = GIT_CLONE_OPTIONS_INIT;
+    cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+    cloneOptions.checkout_opts.progress_cb = clone_progress_cb;
+    cloneOptions.checkout_opts.progress_payload = this;
+    cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
+    cloneOptions.fetch_opts.callbacks.credentials = credentials_cb;
+    cloneOptions.fetch_opts.callbacks.payload = this;
+
+    m_testedTypes = 0;
+
+    if( git_clone( &m_repo, m_URL.ToStdString().c_str(), m_clonePath.ToStdString().c_str(), &cloneOptions ) != 0 )
+    {
+        AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), m_URL ) );
+        return false;
+    }
+
+    if( m_progressReporter )
+        m_progressReporter->Hide();
+
+    m_previousProgress = 0;
+
+    return true;
+}
+
+
+void GIT_CLONE_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
+{
+    ReportProgress( aCurrent, aTotal, aMessage );
+}
diff --git a/common/git/git_clone_handler.h b/common/git/git_clone_handler.h
new file mode 100644
index 0000000000..b90c78f7b5
--- /dev/null
+++ b/common/git/git_clone_handler.h
@@ -0,0 +1,56 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_CLONE_HANDLER_H_
+#define GIT_CLONE_HANDLER_H_
+
+#include <git/kicad_git_common.h>
+#include <git/git_progress.h>
+class GIT_CLONE_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS
+{
+public:
+    GIT_CLONE_HANDLER();
+    ~GIT_CLONE_HANDLER();
+
+    bool PerformClone();
+
+    void SetURL( const wxString& aURL ) { m_URL = aURL; }
+    wxString GetURL() const { return m_URL; }
+
+    void SetBranch( const wxString& aBranch ) { m_branch = aBranch; }
+    wxString GetBranch() const { return m_branch; }
+
+    void SetClonePath( const wxString& aPath ) { m_clonePath = aPath; }
+    wxString GetClonePath() const { return m_clonePath; }
+
+    void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
+
+private:
+
+    wxString m_URL;
+    wxString m_branch;
+    wxString m_clonePath;
+};
+
+
+#endif
\ No newline at end of file
diff --git a/common/git/git_commit_handler.cpp b/common/git/git_commit_handler.cpp
new file mode 100644
index 0000000000..c5b413757a
--- /dev/null
+++ b/common/git/git_commit_handler.cpp
@@ -0,0 +1,49 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_commit_handler.h"
+
+GIT_COMMIT_HANDLER::GIT_COMMIT_HANDLER( git_repository* aRepo ) :
+    KIGIT_COMMON( aRepo )
+{}
+
+GIT_COMMIT_HANDLER::~GIT_COMMIT_HANDLER()
+{}
+
+
+GIT_COMMIT_HANDLER::CommitResult GIT_COMMIT_HANDLER::PerformCommit( const std::vector<std::string>& aFilesToCommit )
+{
+
+    return CommitResult::Success;
+}
+
+std::string GIT_COMMIT_HANDLER::GetErrorString() const
+{
+    return m_errorString;
+}
+
+void GIT_COMMIT_HANDLER::AddErrorString( const std::string& aErrorString )
+{
+    m_errorString += aErrorString;
+}
+
diff --git a/common/git/git_commit_handler.h b/common/git/git_commit_handler.h
new file mode 100644
index 0000000000..ead3484f6e
--- /dev/null
+++ b/common/git/git_commit_handler.h
@@ -0,0 +1,58 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_COMMIT_HANDLER_H
+#define GIT_COMMIT_HANDLER_H
+
+// Define a class to handle git commit operations
+
+#include <git/kicad_git_common.h>
+#include <git2.h>
+
+#include <string>
+#include <vector>
+
+class GIT_COMMIT_HANDLER : public KIGIT_COMMON
+{
+public:
+    GIT_COMMIT_HANDLER( git_repository* aRepo );
+    virtual ~GIT_COMMIT_HANDLER();
+
+    enum class CommitResult
+    {
+        Success,
+        Error,
+        Cancelled
+    };
+
+    CommitResult PerformCommit( const std::vector<std::string>& aFilesToCommit );
+
+    std::string GetErrorString() const;
+
+private:
+    void AddErrorString( const std::string& aErrorString );
+
+    std::string m_errorString;
+};
+
+#endif // GIT_COMMIT_HANDLER_H
\ No newline at end of file
diff --git a/common/git/git_compare_handler.cpp b/common/git/git_compare_handler.cpp
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_compare_handler.h b/common/git/git_compare_handler.h
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_progress.h b/common/git/git_progress.h
new file mode 100644
index 0000000000..b75528e3a4
--- /dev/null
+++ b/common/git/git_progress.h
@@ -0,0 +1,72 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_PROGRESS_H_
+#define GIT_PROGRESS_H_
+
+#include <widgets/wx_progress_reporters.h>
+
+#include <memory>
+
+class GIT_PROGRESS
+{
+public:
+    GIT_PROGRESS() : m_previousProgress( 0 )
+    {
+        m_progressReporter.reset();
+    }
+
+
+    void SetProgressReporter( std::unique_ptr<WX_PROGRESS_REPORTER> aProgressReporter )
+    {
+        m_progressReporter = std::move( aProgressReporter );
+    }
+
+    void ReportProgress( int aCurrent, int aTotal, const wxString& aMessage )
+    {
+
+        if( m_progressReporter )
+        {
+            if( aCurrent == m_previousProgress || aTotal == 0 )
+            {
+                m_progressReporter->Pulse( aMessage );
+            }
+            else
+            {
+                m_progressReporter->SetCurrentProgress( static_cast<double>( aCurrent ) / aTotal );
+                m_progressReporter->Report( aMessage );
+            }
+
+            m_previousProgress = aCurrent;
+            m_progressReporter->KeepRefreshing();
+        }
+    }
+
+
+protected:
+    int m_previousProgress;
+
+    std::unique_ptr<WX_PROGRESS_REPORTER> m_progressReporter;
+};
+
+#endif // GIT_PROGRESS_H__
\ No newline at end of file
diff --git a/common/git/git_pull_handler.cpp b/common/git/git_pull_handler.cpp
new file mode 100644
index 0000000000..d72ffd589c
--- /dev/null
+++ b/common/git/git_pull_handler.cpp
@@ -0,0 +1,343 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_pull_handler.h"
+#include <git/kicad_git_common.h>
+
+#include <iostream>
+
+GIT_PULL_HANDLER::GIT_PULL_HANDLER( git_repository* aRepo ) :  KIGIT_COMMON( aRepo )
+{}
+
+GIT_PULL_HANDLER::~GIT_PULL_HANDLER()
+{}
+
+
+bool GIT_PULL_HANDLER::PerformFetch()
+{
+      // Fetch updates from remote repository
+    git_remote* remote = nullptr;
+
+    if( git_remote_lookup( &remote, m_repo, "origin" ) != 0 )
+    {
+        AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ).ToStdString() );
+        return false;
+    }
+
+    git_remote_callbacks remoteCallbacks = GIT_REMOTE_CALLBACKS_INIT;
+    remoteCallbacks.sideband_progress = progress_cb;
+    remoteCallbacks.transfer_progress = transfer_progress_cb;
+    remoteCallbacks.credentials = credentials_cb;
+    remoteCallbacks.payload = this;
+
+    if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
+    {
+        git_remote_free( remote );
+        AddErrorString( wxString::Format( _( "Could not connect to remote '%s'" ), "origin" ).ToStdString() );
+        return false;
+    }
+
+    git_fetch_options fetchOptions = GIT_FETCH_OPTIONS_INIT;
+    fetchOptions.callbacks = remoteCallbacks;
+
+    if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
+    {
+        git_remote_free( remote );
+        AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s'" ), "origin" ) );
+        return false;
+    }
+
+    git_remote_free( remote );
+
+    return true;
+}
+
+
+PullResult GIT_PULL_HANDLER::PerformPull()
+{
+    PullResult result = PullResult::Success;
+
+    if( !PerformFetch() )
+        return PullResult::Error;
+
+    git_oid pull_merge_oid = {};
+
+    if( git_repository_fetchhead_foreach( m_repo, fetchhead_foreach_cb, &pull_merge_oid ) )
+    {
+        AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
+        return PullResult::Error;
+    }
+
+    git_annotated_commit* fetchhead_commit;
+
+    if( git_annotated_commit_lookup( &fetchhead_commit, m_repo, &pull_merge_oid ) )
+    {
+        AddErrorString( _( "Could not lookup commit" ) );
+        return PullResult::Error;
+    }
+
+    const git_annotated_commit* merge_commits[] = { fetchhead_commit };
+    git_merge_analysis_t merge_analysis;
+    git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
+
+    if( git_merge_analysis( &merge_analysis, &merge_preference, m_repo, merge_commits, 1 ) )
+    {
+        AddErrorString( _( "Could not analyze merge" ) );
+        git_annotated_commit_free( fetchhead_commit );
+        return PullResult::Error;
+    }
+
+    if( !( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL ) )
+        git_annotated_commit_free( fetchhead_commit );
+
+    if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
+    {
+        AddErrorString( _( "Invalid HEAD.  Cannot merge." ) );
+        return PullResult::MergeFailed;
+    }
+
+    // Nothing to do if the repository is up to date
+    if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
+    {
+        git_repository_state_cleanup( m_repo );
+        return PullResult::UpToDate;
+    }
+
+    // Fast-forward is easy, just update the local reference
+    if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
+    {
+        return handleFastForward();
+    }
+
+    if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
+    {
+        PullResult ret = handleMerge( merge_commits, 1 );
+        git_annotated_commit_free( fetchhead_commit );
+        return ret;
+    }
+
+    //TODO: handle merges when they need to be resolved
+
+    return result;
+}
+
+const std::vector<std::pair<std::string, std::vector<CommitDetails>>>& GIT_PULL_HANDLER::GetFetchResults() const {
+    return m_fetchResults;
+}
+
+std::string GIT_PULL_HANDLER::getFirstLineFromCommitMessage( const std::string& aMessage )
+{
+    size_t firstLineEnd = aMessage.find_first_of( '\n' );
+
+    if( firstLineEnd != std::string::npos )
+        return aMessage.substr( 0, firstLineEnd );
+
+    return aMessage;
+}
+
+std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime )
+{
+    char dateBuffer[64];
+    strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", gmtime( &aTime.time ) );
+    return dateBuffer;
+}
+
+
+PullResult GIT_PULL_HANDLER::handleFastForward()
+{
+        // Update local references with fetched data
+        git_reference* updatedRef = nullptr;
+
+        if( git_repository_head( &updatedRef, m_repo ) )
+        {
+            AddErrorString( _( "Could not get repository head" ) );
+            return PullResult::Error;
+        }
+
+        const char* updatedRefName = git_reference_name( updatedRef );
+        git_reference_free( updatedRef );
+
+        git_oid     updatedRefOid;
+        if( git_reference_name_to_id( &updatedRefOid, m_repo, updatedRefName ) )
+        {
+            AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ), updatedRefName ) );
+            return PullResult::Error;
+        }
+
+        git_checkout_options checkoutOptions = GIT_CHECKOUT_OPTIONS_INIT;
+        checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
+        if( git_checkout_head( m_repo, &checkoutOptions ) )
+        {
+            AddErrorString( _( "Failed to perform checkout operation." ) );
+            return PullResult::Error;
+        }
+
+        // Collect commit details for updated references
+        git_revwalk* revWalker = nullptr;
+        git_revwalk_new( &revWalker, m_repo );
+        git_revwalk_sorting( revWalker, GIT_SORT_TIME );
+        git_revwalk_push_glob( revWalker, updatedRefName );
+
+        git_oid commitOid;
+        while( git_revwalk_next( &commitOid, revWalker ) == 0 )
+        {
+            git_commit* commit = nullptr;
+
+            if( git_commit_lookup( &commit, m_repo, &commitOid ) )
+            {
+                AddErrorString( wxString::Format( _( "Could not lookup commit '{}'" ), git_oid_tostr_s( &commitOid ) ) );
+                git_revwalk_free( revWalker );
+                return PullResult::Error;
+            }
+
+            CommitDetails details;
+            details.m_sha = git_oid_tostr_s( &commitOid );
+            details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
+            details.m_author = git_commit_author( commit )->name;
+            details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
+
+            std::pair<std::string, std::vector<CommitDetails>>& branchCommits =
+                    m_fetchResults.emplace_back();
+            branchCommits.first = updatedRefName;
+            branchCommits.second.push_back( details );
+
+            //TODO: log these to the parent
+            git_commit_free( commit );
+        }
+
+        git_revwalk_free( revWalker );
+
+        git_repository_state_cleanup( m_repo );
+        return PullResult::FastForward;
+}
+
+
+PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHeads,
+                                          size_t                       aMergeHeadsCount )
+{
+    git_merge_options    merge_opts = GIT_MERGE_OPTIONS_INIT;
+    git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+    checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+    if( git_merge( m_repo, aMergeHeads, aMergeHeadsCount, &merge_opts, &checkout_opts ) )
+    {
+        AddErrorString( _( "Could not merge commits" ) );
+        return PullResult::Error;
+    }
+
+    // Get the repository index
+    git_index* index;
+    if( git_repository_index( &index, m_repo ) )
+    {
+        AddErrorString( _( "Could not get repository index" ) );
+        return PullResult::Error;
+    }
+
+    // Check for conflicts
+    git_index_conflict_iterator* conflicts;
+    if( git_index_conflict_iterator_new( &conflicts, index ) )
+    {
+        AddErrorString( _( "Could not get conflict iterator" ) );
+        return PullResult::Error;
+    }
+
+    const git_index_entry* ancestor;
+    const git_index_entry* our;
+    const git_index_entry* their;
+    std::vector<ConflictData> conflict_data;
+
+    while( git_index_conflict_next( &ancestor, &our, &their, conflicts ) == 0 )
+    {
+        // Case 3: Both files have changed
+        if( ancestor && our && their )
+        {
+            ConflictData conflict_datum;
+            conflict_datum.filename = our->path;
+            conflict_datum.our_oid = our->id;
+            conflict_datum.their_oid = their->id;
+            conflict_datum.our_commit_time = our->mtime.seconds;
+            conflict_datum.their_commit_time = their->mtime.seconds;
+            conflict_datum.our_status = _( "Changed" );
+            conflict_datum.their_status = _( "Changed" );
+
+            conflict_data.push_back( conflict_datum );
+        }
+        // Case 4: File added in both ours and theirs
+        else if( !ancestor && our && their )
+        {
+            ConflictData conflict_datum;
+            conflict_datum.filename = our->path;
+            conflict_datum.our_oid = our->id;
+            conflict_datum.their_oid = their->id;
+            conflict_datum.our_commit_time = our->mtime.seconds;
+            conflict_datum.their_commit_time = their->mtime.seconds;
+            conflict_datum.our_status = _( "Added" );
+            conflict_datum.their_status = _( "Added" );
+
+            conflict_data.push_back( conflict_datum );
+        }
+        // Case 1: Remote file has changed or been added, local file has not
+        else if( their && !our )
+        {
+            // Accept their changes
+            git_index_add( index, their );
+        }
+        // Case 2: Local file has changed or been added, remote file has not
+        else if( our && !their )
+        {
+            // Accept our changes
+            git_index_add( index, our );
+        }
+        else
+        {
+            ConflictData conflict_datum;
+            conflict_datum.filename = our->path;
+            conflict_datum.our_oid = our->id;
+            conflict_datum.their_oid = their->id;
+            conflict_datum.our_commit_time = our->mtime.seconds;
+            conflict_datum.their_commit_time = their->mtime.seconds;
+            conflict_datum.our_status = _( "Other" );
+            conflict_datum.their_status = _( "Other" );
+
+            conflict_data.push_back( conflict_datum );
+        }
+    }
+
+    if( conflict_data.empty() )
+    {
+        git_index_conflict_cleanup( index );
+        git_index_write( index );
+    }
+
+    git_index_conflict_iterator_free( conflicts );
+    git_index_free( index );
+
+    return conflict_data.empty() ? PullResult::Success : PullResult::MergeFailed;
+}
+
+
+void GIT_PULL_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
+{
+    ReportProgress( aCurrent, aTotal, aMessage );
+}
diff --git a/common/git/git_pull_handler.h b/common/git/git_pull_handler.h
new file mode 100644
index 0000000000..0486340021
--- /dev/null
+++ b/common/git/git_pull_handler.h
@@ -0,0 +1,100 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GITPULLHANDLER_HPP
+#define GITPULLHANDLER_HPP
+
+#include <git2.h>
+#include <functional>
+#include <vector>
+#include <string>
+
+#include "kicad_git_common.h"
+
+#include <git/git_progress.h>
+
+// Structure to store commit details
+struct CommitDetails
+{
+    std::string m_sha;
+    std::string m_firstLine;
+    std::string m_author;
+    std::string m_date;
+};
+
+// Enum for result codes
+enum class PullResult
+{
+    Success,
+    Error,
+    UpToDate,
+    FastForward,
+    MergeFailed
+};
+
+struct ConflictData
+{
+    std::string filename;
+    std::string our_status;
+    std::string their_status;
+    git_oid our_oid;
+    git_oid their_oid;
+    git_time_t our_commit_time;
+    git_time_t their_commit_time;
+    bool use_ours; // Flag indicating user's choice (true = ours, false = theirs)
+};
+
+
+class GIT_PULL_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS
+{
+public:
+    GIT_PULL_HANDLER( git_repository* aRepo );
+    ~GIT_PULL_HANDLER();
+
+    PullResult PerformPull();
+
+    bool PerformFetch();
+
+    const std::vector<std::pair<std::string, std::vector<CommitDetails>>>& GetFetchResults() const;
+
+    // Set the callback function for conflict resolution
+    void SetConflictCallback(
+            std::function<int( std::vector<ConflictData>& aConflicts )> aCallback )
+    {
+        m_conflictCallback = aCallback;
+    }
+
+    void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
+
+private:
+    std::string getFirstLineFromCommitMessage( const std::string& aMessage );
+    std::string getFormattedCommitDate( const git_time& aTime );private:
+
+    PullResult handleFastForward();
+    PullResult handleMerge( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount);
+
+    std::vector<std::pair<std::string, std::vector<CommitDetails>>> m_fetchResults;
+    std::function<int( std::vector<ConflictData>& aConflicts )>     m_conflictCallback;
+};
+
+#endif // GITPULLHANDLER_HPP
diff --git a/common/git/git_push_handler.cpp b/common/git/git_push_handler.cpp
new file mode 100644
index 0000000000..325ce2687f
--- /dev/null
+++ b/common/git/git_push_handler.cpp
@@ -0,0 +1,79 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_push_handler.h"
+#include <git/kicad_git_common.h>
+
+#include <iostream>
+
+GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( git_repository* aRepo ) :  KIGIT_COMMON( aRepo )
+{}
+
+GIT_PUSH_HANDLER::~GIT_PUSH_HANDLER()
+{}
+
+PushResult GIT_PUSH_HANDLER::PerformPush()
+{
+    PushResult result = PushResult::Success;
+
+    // Fetch updates from remote repository
+    git_remote* remote = nullptr;
+
+    if( git_remote_lookup( &remote, m_repo, "origin" ) != 0 )
+    {
+        AddErrorString( _( "Could not lookup remote" ) );
+        return PushResult::Error;
+    }
+
+    git_remote_callbacks remoteCallbacks = GIT_REMOTE_CALLBACKS_INIT;
+    remoteCallbacks.sideband_progress = progress_cb;
+    remoteCallbacks.transfer_progress = transfer_progress_cb;
+    remoteCallbacks.update_tips = update_cb;
+    remoteCallbacks.push_transfer_progress = push_transfer_progress_cb;
+    remoteCallbacks.payload = this;
+
+    if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, nullptr, nullptr ) )
+    {
+        git_remote_free( remote );
+        AddErrorString( _( "Could not connect to remote" ) );
+        return PushResult::Error;
+    }
+
+    git_push_options pushOptions = GIT_PUSH_OPTIONS_INIT;
+    pushOptions.callbacks = remoteCallbacks;
+
+    if( git_remote_push( remote, nullptr, &pushOptions ) )
+    {
+        git_remote_free( remote );
+        AddErrorString( _( "Could not push to remote" ) );
+        return PushResult::Error;
+    }
+
+    return result;
+}
+
+
+void GIT_PUSH_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
+{
+    ReportProgress( aCurrent, aTotal, aMessage );
+}
diff --git a/common/git/git_push_handler.h b/common/git/git_push_handler.h
new file mode 100644
index 0000000000..4a159e5397
--- /dev/null
+++ b/common/git/git_push_handler.h
@@ -0,0 +1,57 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GITPUSHHANDLER_HPP
+#define GITPUSHHANDLER_HPP
+
+#include <git2.h>
+#include <functional>
+#include <vector>
+#include <string>
+
+#include "kicad_git_common.h"
+#include <git/git_progress.h>
+
+// Enum for result codes
+enum class PushResult
+{
+    Success,
+    Error,
+    UpToDate
+};
+
+class GIT_PUSH_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS
+{
+public:
+    GIT_PUSH_HANDLER( git_repository* aRepo );
+    ~GIT_PUSH_HANDLER();
+
+    PushResult PerformPush();
+
+    void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
+
+private:
+
+};
+
+#endif // GITPUSHHANDLER_HPP
diff --git a/common/git/git_remove_from_index_handler.cpp b/common/git/git_remove_from_index_handler.cpp
new file mode 100644
index 0000000000..10ccbfa621
--- /dev/null
+++ b/common/git/git_remove_from_index_handler.cpp
@@ -0,0 +1,98 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <wx/string.h>
+#include <wx/log.h>
+
+#include "git_remove_from_index_handler.h"
+
+GIT_REMOVE_FROM_INDEX_HANDLER::GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository )
+{
+    m_repository = aRepository;
+    m_filesToRemove.clear();
+}
+
+GIT_REMOVE_FROM_INDEX_HANDLER::~GIT_REMOVE_FROM_INDEX_HANDLER()
+{
+}
+
+bool GIT_REMOVE_FROM_INDEX_HANDLER::RemoveFromIndex( const wxString& aFilePath )
+{
+    // Test if file is currently in the index
+
+    git_index* index = nullptr;
+    size_t at_pos = 0;
+
+    if( git_repository_index( &index, m_repository ) != 0 )
+    {
+        wxLogError( "Failed to get repository index" );
+        return false;
+    }
+
+    if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 )
+    {
+        git_index_free( index );
+        wxLogError( "Failed to find index entry for %s", aFilePath );
+        return false;
+    }
+
+    git_index_free( index );
+
+    m_filesToRemove.push_back( aFilePath );
+    return true;
+}
+
+void GIT_REMOVE_FROM_INDEX_HANDLER::PerformRemoveFromIndex()
+{
+    for( auto& file : m_filesToRemove )
+    {
+        git_index* index = nullptr;
+        git_oid oid;
+
+        if( git_repository_index( &index, m_repository ) != 0 )
+        {
+            wxLogError( "Failed to get repository index" );
+            return;
+        }
+
+        if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 )
+        {
+            wxLogError( "Failed to remove index entry for %s", file );
+            return;
+        }
+
+        if( git_index_write( index ) != 0 )
+        {
+            wxLogError( "Failed to write index" );
+            return;
+        }
+
+        if( git_index_write_tree( &oid, index ) != 0 )
+        {
+            wxLogError( "Failed to write index tree" );
+            return;
+        }
+
+        git_index_free( index );
+    }
+}
\ No newline at end of file
diff --git a/common/git/git_remove_from_index_handler.h b/common/git/git_remove_from_index_handler.h
new file mode 100644
index 0000000000..62aa08392b
--- /dev/null
+++ b/common/git/git_remove_from_index_handler.h
@@ -0,0 +1,50 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_REMOVE_FROM_INDEX_HANDLER_H_
+#define GIT_REMOVE_FROM_INDEX_HANDLER_H_
+
+#include <git2.h>
+#include <vector>
+
+class wxString;
+
+class GIT_REMOVE_FROM_INDEX_HANDLER
+{
+public:
+    GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository );
+    virtual ~GIT_REMOVE_FROM_INDEX_HANDLER();
+
+    bool RemoveFromIndex( const wxString& aFilePath );
+
+    void PerformRemoveFromIndex();
+
+private:
+
+    git_repository* m_repository;
+
+    std::vector<wxString> m_filesToRemove;
+};
+
+
+#endif /* GIT_REMOVE_FROM_INDEX_HANDLER_H_ */
diff --git a/common/git/git_remove_vcs_handler.cpp b/common/git/git_remove_vcs_handler.cpp
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_remove_vcs_handler.h b/common/git/git_remove_vcs_handler.h
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_resolve_conflict_handler.cpp b/common/git/git_resolve_conflict_handler.cpp
new file mode 100644
index 0000000000..35117ab7e6
--- /dev/null
+++ b/common/git/git_resolve_conflict_handler.cpp
@@ -0,0 +1,39 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_resolve_conflict_handler.h"
+
+GIT_RESOLVE_CONFLICT_HANDLER::GIT_RESOLVE_CONFLICT_HANDLER( git_repository* aRepository )
+{
+    m_repository = aRepository;
+}
+
+GIT_RESOLVE_CONFLICT_HANDLER::~GIT_RESOLVE_CONFLICT_HANDLER()
+{
+}
+
+bool GIT_RESOLVE_CONFLICT_HANDLER::PerformResolveConflict()
+{
+        return true;
+}
+
diff --git a/common/git/git_resolve_conflict_handler.h b/common/git/git_resolve_conflict_handler.h
new file mode 100644
index 0000000000..a2bfe6754b
--- /dev/null
+++ b/common/git/git_resolve_conflict_handler.h
@@ -0,0 +1,43 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_RESOLVE_CONFLICT_HANDLER_H
+#define GIT_RESOLVE_CONFLICT_HANDLER_H
+
+#include <git2.h>
+
+class wxString;
+
+class GIT_RESOLVE_CONFLICT_HANDLER
+{
+public:
+    GIT_RESOLVE_CONFLICT_HANDLER( git_repository* aRepository );
+    virtual ~GIT_RESOLVE_CONFLICT_HANDLER();
+
+    bool PerformResolveConflict();
+
+private:
+    git_repository* m_repository;
+};
+
+#endif // GIT_RESOLVE_CONFLICT_HANDLER_H
diff --git a/common/git/git_revert_handler.cpp b/common/git/git_revert_handler.cpp
new file mode 100644
index 0000000000..baf6b2a327
--- /dev/null
+++ b/common/git/git_revert_handler.cpp
@@ -0,0 +1,108 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_revert_handler.h"
+
+#include <wx/log.h>
+#include <wx/string.h>
+
+GIT_REVERT_HANDLER::GIT_REVERT_HANDLER( git_repository* aRepository )
+{
+    m_repository = aRepository;
+}
+
+GIT_REVERT_HANDLER::~GIT_REVERT_HANDLER()
+{
+}
+
+bool GIT_REVERT_HANDLER::Revert( const wxString& aFilePath )
+{
+    m_filesToRevert.push_back( aFilePath );
+    return true;
+}
+
+static void checkout_progress_cb(const char *path, size_t cur, size_t tot, void *payload)
+{
+    wxLogDebug( "checkout_progress_cb: %s %zu/%zu", path, cur, tot );
+}
+
+
+static int checkout_notify_cb(git_checkout_notify_t why, const char *path,
+                              const git_diff_file *baseline,
+                              const git_diff_file *target,
+                              const git_diff_file *workdir, void *payload)
+{
+    GIT_REVERT_HANDLER* handler = static_cast<GIT_REVERT_HANDLER*>(payload);
+
+    if( why & ( GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_IGNORED | GIT_CHECKOUT_NOTIFY_UPDATED ) )
+        handler->PushFailedFile( path );
+
+    return 0;
+}
+
+void GIT_REVERT_HANDLER::PerformRevert()
+{
+    git_object* head_commit = NULL;
+    git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+    // Get the HEAD commit
+    if (git_revparse_single(&head_commit, m_repository, "HEAD") != 0) {
+        // Handle error. If we cannot get the HEAD, then there's no point proceeding.
+        return;
+    }
+
+    opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+    char** paths =  new char*[m_filesToRevert.size()];
+
+    for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ )
+    {
+        // Set paths to the specific file
+        paths[ii] = wxStrdup( m_filesToRevert[ii].ToUTF8() );
+    }
+
+    git_strarray arr = { paths, m_filesToRevert.size() };
+
+    opts.paths = arr;
+    opts.progress_cb = checkout_progress_cb;
+    opts.notify_cb = checkout_notify_cb;
+    opts.notify_payload = static_cast<void*>(this);
+
+    // Attempt to checkout the file(s)
+    if (git_checkout_tree(m_repository, head_commit, &opts) != 0)
+    {
+        const git_error *e = git_error_last();
+        if (e)
+        {
+            wxLogError( "Checkout failed: %d: %s", e->klass, e->message );
+        }
+    }
+
+    // Free the HEAD commit
+    for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ )
+        delete( paths[ii] );
+
+    delete[] paths;
+
+    git_object_free(head_commit);
+}
+
diff --git a/common/git/git_revert_handler.h b/common/git/git_revert_handler.h
new file mode 100644
index 0000000000..c004834650
--- /dev/null
+++ b/common/git/git_revert_handler.h
@@ -0,0 +1,54 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_REVERT_HANDLER_H_
+#define GIT_REVERT_HANDLER_H_
+
+#include <git2.h>
+#include <vector>
+
+class wxString;
+
+class GIT_REVERT_HANDLER
+{
+public:
+    GIT_REVERT_HANDLER( git_repository* aRepository );
+    virtual ~GIT_REVERT_HANDLER();
+
+    bool Revert( const wxString& aFilePath );
+
+    void PerformRevert();
+
+    void PushFailedFile( const wxString& aFilePath )
+    {
+        m_filesFailedToRevert.push_back( aFilePath );
+    }
+
+private:
+    git_repository* m_repository;
+
+    std::vector<wxString> m_filesToRevert;
+    std::vector<wxString> m_filesFailedToRevert;
+};
+
+#endif /* GIT_REVERT_HANDLER_H_ */
\ No newline at end of file
diff --git a/common/git/git_switch_branch_handler.cpp b/common/git/git_switch_branch_handler.cpp
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_switch_branch_handler.h b/common/git/git_switch_branch_handler.h
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/git/git_sync_handler.cpp b/common/git/git_sync_handler.cpp
new file mode 100644
index 0000000000..57f81c80e2
--- /dev/null
+++ b/common/git/git_sync_handler.cpp
@@ -0,0 +1,44 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "git_sync_handler.h"
+
+#include <wx/string.h>
+
+#include <git2.h>
+
+GIT_SYNC_HANDLER::GIT_SYNC_HANDLER( git_repository* aRepository )
+{
+    m_repository = aRepository;
+}
+
+
+GIT_SYNC_HANDLER::~GIT_SYNC_HANDLER()
+{
+}
+
+
+bool GIT_SYNC_HANDLER::PerformSync()
+{
+    return true;
+}
diff --git a/common/git/git_sync_handler.h b/common/git/git_sync_handler.h
new file mode 100644
index 0000000000..234fcc7fe1
--- /dev/null
+++ b/common/git/git_sync_handler.h
@@ -0,0 +1,43 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef GIT_SYNC_HANDLER_H_
+#define GIT_SYNC_HANDLER_H_
+
+#include <git2.h>
+
+class wxString;
+
+class GIT_SYNC_HANDLER
+{
+public:
+    GIT_SYNC_HANDLER( git_repository* aRepository );
+    virtual ~GIT_SYNC_HANDLER();
+
+    bool PerformSync();
+
+private:
+    git_repository* m_repository;
+};
+
+#endif /* GIT_SYNC_HANDLER_H_ */
\ No newline at end of file
diff --git a/common/git/kicad_git_blob_reader.h b/common/git/kicad_git_blob_reader.h
new file mode 100644
index 0000000000..fd28bf0474
--- /dev/null
+++ b/common/git/kicad_git_blob_reader.h
@@ -0,0 +1,97 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <streambuf>
+#include <istream>
+#include <string>
+#include <git2.h>
+
+#include <richio.h>
+
+
+class BLOB_BUFFER_STREAM : public std::streambuf
+{
+    public:
+        BLOB_BUFFER_STREAM( git_blob* aBlob )
+        {
+            // Yay C++
+            setg( const_cast<char*>( static_cast<const char*>( git_blob_rawcontent( aBlob ) ) ),
+                  const_cast<char*>( static_cast<const char*>( git_blob_rawcontent( aBlob ) ) ),
+                  const_cast<char*>( static_cast<const char*>( git_blob_rawcontent( aBlob ) ) ) + git_blob_rawsize( aBlob ) );
+        }
+
+        ~BLOB_BUFFER_STREAM() override
+        {
+        }
+
+        int overflow( int c ) override
+        {
+            return traits_type::eof();
+        }
+
+        std::streamsize xsputn( const char* s, std::streamsize n ) override
+        {
+            return 0;
+        }
+};
+
+// Build a class that implements LINE_READER for git_blobs
+class BLOB_READER : public LINE_READER
+{
+    public:
+        BLOB_READER( git_blob* aBlob ) : m_blob( aBlob )
+        {
+            m_stream = new BLOB_BUFFER_STREAM( m_blob );
+            m_istream = new std::istream( m_stream );
+            m_line = nullptr;
+            m_lineNum = 0;
+        }
+
+        ~BLOB_READER() override
+        {
+            delete m_istream;
+            delete m_stream;
+        }
+
+        char* ReadLine() override
+        {
+            getline( *m_istream, m_buffer );
+
+            m_buffer.append( 1, '\n' );
+
+            m_length = m_buffer.size();
+            m_line = (char*) m_buffer.data(); //ew why no const??
+
+            // lineNum is incremented even if there was no line read, because this
+            // leads to better error reporting when we hit an end of file.
+            ++m_lineNum;
+
+            return m_istream->eof() ? nullptr : m_line;
+        }
+
+    private:
+        git_blob* m_blob;
+        BLOB_BUFFER_STREAM* m_stream;
+        std::istream* m_istream;
+        std::string   m_buffer;
+};
\ No newline at end of file
diff --git a/common/git/kicad_git_common.cpp b/common/git/kicad_git_common.cpp
new file mode 100644
index 0000000000..2e1ce4b2e2
--- /dev/null
+++ b/common/git/kicad_git_common.cpp
@@ -0,0 +1,541 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "kicad_git_common.h"
+
+#include <wx/filename.h>
+#include <wx/log.h>
+#include <map>
+#include <vector>
+
+KIGIT_COMMON::KIGIT_COMMON( git_repository* aRepo ) : m_repo( aRepo )
+{}
+
+KIGIT_COMMON::~KIGIT_COMMON()
+{}
+
+git_repository* KIGIT_COMMON::GetRepo() const
+{
+    return m_repo;
+}
+
+wxString KIGIT_COMMON::GetCurrentBranchName() const
+{
+    git_reference* head = nullptr;
+
+    int retval = git_repository_head( &head, m_repo );
+
+    if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
+        return wxEmptyString;
+
+    git_reference *branch;
+
+    if( git_reference_resolve( &branch, head ) )
+    {
+        git_reference_free( head );
+        return wxEmptyString;
+    }
+
+    git_reference_free( head );
+    const char* branchName = "";
+
+    if( git_branch_name( &branchName, branch ) )
+    {
+        git_reference_free( branch );
+        return wxEmptyString;
+    }
+
+    git_reference_free( branch );
+
+    return branchName;
+}
+
+
+std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
+{
+    std::vector<wxString> branchNames;
+    std::map<git_time_t, wxString> branchNamesMap;
+    wxString firstName;
+
+    git_branch_iterator* branchIterator = nullptr;
+
+    if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
+        return branchNames;
+
+    git_reference* branchReference = nullptr;
+    git_branch_t branchType;
+
+    while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
+    {
+        const char* branchName = "";
+
+        if( git_branch_name( &branchName, branchReference ) )
+            continue;
+
+        const git_oid* commitId = git_reference_target( branchReference );
+
+        git_commit* commit = nullptr;
+
+        if( git_commit_lookup( &commit, m_repo, commitId ) )
+            continue;
+
+        git_time_t commitTime = git_commit_time( commit );
+
+        if( git_branch_is_head( branchReference ) )
+            firstName = branchName;
+        else
+            branchNamesMap.emplace( commitTime, branchName );
+
+        git_commit_free( commit );
+        git_reference_free( branchReference );
+    }
+
+    git_branch_iterator_free( branchIterator );
+
+    // Add the current branch to the top of the list
+    if( !firstName.IsEmpty() )
+        branchNames.push_back( firstName );
+
+    // Add the remaining branches in order from newest to oldest
+    for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
+        branchNames.push_back( rit->second );
+
+    return branchNames;
+}
+
+
+std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
+{
+    std::vector<wxString> projDirs;
+
+    git_oid oid;
+    git_commit* commit;
+    git_tree *tree;
+
+    if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
+    {
+        wxLogError( "An error occurred: %s", git_error_last()->message );
+        return projDirs;
+    }
+
+    if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
+    {
+        wxLogError( "An error occurred: %s", git_error_last()->message );
+        return projDirs;
+    }
+
+    if( git_commit_tree( &tree, commit ) != GIT_OK )
+    {
+        wxLogError( "An error occurred: %s", git_error_last()->message );
+        return projDirs;
+    }
+
+    // Define callback
+    git_tree_walk(
+            tree, GIT_TREEWALK_PRE,
+            []( const char* root, const git_tree_entry* entry, void* payload )
+            {
+                std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
+                wxFileName             root_fn( git_tree_entry_name( entry ) );
+
+                root_fn.SetPath( root );
+
+                if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
+                    && ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
+                {
+                    prjs->push_back( root_fn.GetFullPath() );
+                }
+
+                return 0; // continue walking
+            },
+            &projDirs );
+
+    git_tree_free( tree );
+    git_commit_free( commit );
+
+    std::sort( projDirs.begin(), projDirs.end(),
+               []( const wxString& a, const wxString& b )
+               {
+                    int a_freq = a.Freq( wxFileName::GetPathSeparator() );
+                    int b_freq = b.Freq( wxFileName::GetPathSeparator() );
+
+                    if( a_freq == b_freq )
+                        return a < b;
+                    else
+                        return a_freq < b_freq;
+
+               } );
+
+    return projDirs;
+}
+
+
+std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
+{
+    auto get_modified_files = [&]( git_oid* from_oid, git_oid* to_oid ) -> std::set<wxString>
+    {
+        std::set<wxString> modified_set;
+        git_revwalk* walker = nullptr;
+
+        if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
+            return modified_set;
+
+        if( ( git_revwalk_push( walker, from_oid ) != GIT_OK )
+            || ( git_revwalk_hide( walker, to_oid ) != GIT_OK ) )
+        {
+            git_revwalk_free( walker );
+            return modified_set;
+        }
+
+        git_oid     oid;
+        git_commit* commit;
+
+        // iterate over all local commits not in remote
+        while( git_revwalk_next( &oid, walker ) == GIT_OK )
+        {
+            if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
+                continue;
+
+            git_tree *tree, *parent_tree = nullptr;
+            if( git_commit_tree( &tree, commit ) != GIT_OK )
+            {
+                git_commit_free( commit );
+                continue;
+            }
+
+            // get parent commit tree to diff against
+            if( !git_commit_parentcount( commit ) )
+            {
+                git_tree_free( tree );
+                git_commit_free( commit );
+                continue;
+            }
+
+
+            git_commit* parent;
+            if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
+            {
+                git_tree_free( tree );
+                git_commit_free( commit );
+                continue;
+            }
+
+
+            if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
+            {
+                git_tree_free( tree );
+                git_commit_free( commit );
+                git_commit_free( parent );
+                continue;
+            }
+
+
+            git_diff*        diff;
+            git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+
+            if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
+            {
+                size_t num_deltas = git_diff_num_deltas( diff );
+
+                for( size_t i = 0; i < num_deltas; ++i )
+                {
+                    const git_diff_delta* delta = git_diff_get_delta( diff, i );
+                    modified_set.insert( delta->new_file.path );
+                }
+
+                git_diff_free( diff );
+            }
+
+            git_tree_free( parent_tree );
+            git_commit_free( parent );
+            git_tree_free( tree );
+            git_commit_free( commit );
+        }
+
+        git_revwalk_free( walker );
+
+        return modified_set;
+    };
+
+    std::pair<std::set<wxString>,std::set<wxString>> modified_files;
+
+    if( !m_repo )
+        return modified_files;
+
+    git_reference* head = nullptr;
+    git_reference* remote_head = nullptr;
+
+    if( git_repository_head( &head, m_repo ) != GIT_OK )
+        return modified_files;
+
+    if( git_branch_upstream( &remote_head, head ) != GIT_OK )
+    {
+        git_reference_free( head );
+        return modified_files;
+    }
+
+    git_oid head_oid = *git_reference_target( head );
+    git_oid remote_oid = *git_reference_target( remote_head );
+
+    git_reference_free( head );
+    git_reference_free( remote_head );
+
+    modified_files.first = get_modified_files( &head_oid, &remote_oid );
+    modified_files.second = get_modified_files( &remote_oid, &head_oid );
+
+    return modified_files;
+}
+
+
+bool KIGIT_COMMON::HasLocalCommits() const
+{
+    if( !m_repo )
+        return false;
+
+    git_reference* head = nullptr;
+    git_reference* remote_head = nullptr;
+
+    if( git_repository_head( &head, m_repo ) != GIT_OK )
+        return false;
+
+    if( git_branch_upstream( &remote_head, head ) != GIT_OK )
+    {
+        git_reference_free( head );
+        return false;
+    }
+
+    git_oid head_oid = *git_reference_target( head );
+    git_oid remote_oid = *git_reference_target( remote_head );
+
+    git_reference_free( head );
+    git_reference_free( remote_head );
+
+    git_revwalk* walker = nullptr;
+
+    if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
+        return false;
+
+    if( ( git_revwalk_push( walker, &head_oid ) != GIT_OK )
+        || ( git_revwalk_hide( walker, &remote_oid ) != GIT_OK ) )
+    {
+        git_revwalk_free( walker );
+        return false;
+    }
+
+    git_oid oid;
+
+    // If we can't walk to the next commit, then we are at or behind the remote
+    if( git_revwalk_next( &oid, walker ) != GIT_OK )
+    {
+        git_revwalk_free( walker );
+        return false;
+    }
+
+    git_revwalk_free( walker );
+    return true;
+}
+
+
+bool KIGIT_COMMON::HasPushAndPullRemote() const
+{
+    git_remote* remote = nullptr;
+
+    if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
+    {
+        return false;
+    }
+
+    // Get the URLs associated with the remote
+    const char* fetch_url = git_remote_url( remote );
+    const char* push_url = git_remote_pushurl( remote );
+
+    // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
+    if( !push_url )
+    {
+        push_url = fetch_url;
+    }
+
+    // Clean up the remote object
+    git_remote_free( remote );
+
+    // Check if both URLs are valid (i.e., not NULL)
+    return fetch_url && push_url;
+}
+
+
+extern "C" int fetchhead_foreach_cb( const char*, const char*,
+                                     const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
+{
+    if( aIsMerge )
+        git_oid_cpy( (git_oid*) aPayload, aOID );
+
+    return 0;
+}
+
+
+extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* data )
+{
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
+
+    wxString progressMessage( aStr );
+    parent->UpdateProgress( aLen, aTotal, progressMessage );
+}
+
+
+extern "C" int progress_cb( const char* str, int len, void* data )
+{
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
+
+    wxString progressMessage( str, len );
+    parent->UpdateProgress( 0, 0, progressMessage );
+
+    return 0;
+}
+
+extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
+{
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
+    wxString      progressMessage = wxString::Format( _( "Received %u of %u objects" ),
+                                                      aStats->received_objects, aStats->total_objects );
+
+    parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
+
+    return 0;
+}
+
+extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
+                          void* aPayload )
+{
+    constexpr int cstring_len = 8;
+    char          a_str[cstring_len + 1];
+    char          b_str[cstring_len + 1];
+
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
+    wxString      status;
+
+    git_oid_tostr( b_str, cstring_len, aSecond );
+
+    if( !git_oid_is_zero( aFirst ) )
+    {
+        git_oid_tostr( a_str, cstring_len, aFirst );
+        status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
+    }
+    else
+    {
+        status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
+    }
+
+    parent->UpdateProgress( 0, 0, status );
+
+    return 0;
+}
+
+
+extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
+                                          void* aPayload )
+{
+    int64_t           progress = 100;
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
+
+    if( aTotal != 0 )
+    {
+        progress = ( aCurrent * 100 ) / aTotal;
+    }
+
+    wxString progressMessage = wxString::Format( _( "Writing objects: %d%% (%d/%d), %d bytes" ),
+                                                 progress, aCurrent, aTotal, aBytes );
+    parent->UpdateProgress( aCurrent, aTotal, progressMessage );
+
+    return 0;
+}
+
+
+extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
+{
+    KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
+    wxString      status( aStatus );
+
+    if( !status.IsEmpty() )
+    {
+        wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
+        parent->UpdateProgress( 0, 0, statusMessage );
+    }
+    else
+    {
+        wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
+        parent->UpdateProgress( 0, 0, statusMessage );
+    }
+
+    return 0;
+}
+
+
+extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
+                                unsigned int aAllowedTypes, void* aPayload )
+{
+    KIGIT_COMMON* parent = static_cast<KIGIT_COMMON*>( aPayload );
+
+    if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL )
+        return GIT_PASSTHROUGH;
+
+    if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
+        && !( parent->TestedTypes() & GIT_CREDTYPE_USERNAME ) )
+    {
+        wxString username = parent->GetUsername().Trim().Trim( false );
+        git_cred_username_new( aOut, username.ToStdString().c_str() );
+        parent->TestedTypes() |= GIT_CREDTYPE_USERNAME;
+    }
+    else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS
+                && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
+                && !( parent->TestedTypes() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
+    {
+        wxString username = parent->GetUsername().Trim().Trim( false );
+        wxString password = parent->GetPassword().Trim().Trim( false );
+
+        git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
+                                            password.ToStdString().c_str() );
+        parent->TestedTypes() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+    }
+    else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH
+                && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
+                && !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
+    {
+        // SSH key authentication
+        wxString sshKey = parent->GetSSHKey();
+        wxString sshPubKey = sshKey + ".pub";
+        wxString username = parent->GetUsername().Trim().Trim( false );
+        wxString password = parent->GetPassword().Trim().Trim( false );
+
+        git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
+                                sshPubKey.ToStdString().c_str(),
+                                sshKey.ToStdString().c_str(),
+                                password.ToStdString().c_str() );
+        parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
+    }
+    else
+    {
+        return GIT_PASSTHROUGH;
+    }
+
+    return GIT_OK;
+};
\ No newline at end of file
diff --git a/common/git/kicad_git_common.h b/common/git/kicad_git_common.h
new file mode 100644
index 0000000000..40e7098f41
--- /dev/null
+++ b/common/git/kicad_git_common.h
@@ -0,0 +1,145 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef _GIT_COMMON_H_
+#define _GIT_COMMON_H_
+
+#include <git/kicad_git_errors.h>
+
+#include <git2.h>
+#include <set>
+
+#include <wx/string.h>
+
+class KIGIT_COMMON : public KIGIT_ERRORS
+{
+
+public:
+    KIGIT_COMMON( git_repository* aRepo );
+    ~KIGIT_COMMON();
+
+    git_repository* GetRepo() const;
+
+    void SetRepo( git_repository* aRepo )
+    {
+        m_repo = aRepo;
+    }
+
+    wxString GetCurrentBranchName() const;
+
+    std::vector<wxString> GetBranchNames() const;
+
+    virtual void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) {};
+
+    /**
+     * Return a vector of project files in the repository.  Sorted by the depth of
+     * the project file in the directory tree
+     *
+     * @return std::vector<wxString> of project files
+     */
+    std::vector<wxString> GetProjectDirs();
+
+    /**
+     * Return a pair of sets of files that differ locally from the remote repository
+     * The first set is files that have been committed locally but not pushed
+     * The second set is files that have been committed remotely but not pulled
+    */
+    std::pair<std::set<wxString>,std::set<wxString>> GetDifferentFiles() const;
+
+    enum class GIT_STATUS
+    {
+        GIT_STATUS_UNTRACKED,
+        GIT_STATUS_CURRENT,
+        GIT_STATUS_MODIFIED,        // File changed but not committed to local repository
+        GIT_STATUS_ADDED,
+        GIT_STATUS_DELETED,
+        GIT_STATUS_BEHIND,          // File changed in remote repository but not in local
+        GIT_STATUS_AHEAD,           // File changed in local repository but not in remote
+        GIT_STATUS_CONFLICTED,
+        GIT_STATUS_LAST
+    };
+
+    enum class GIT_CONN_TYPE
+    {
+        GIT_CONN_HTTPS = 0,
+        GIT_CONN_SSH,
+        GIT_CONN_LOCAL,
+        GIT_CONN_LAST
+    };
+
+    wxString GetUsername() const { return m_username; }
+    wxString GetPassword() const { return m_password; }
+    wxString GetSSHKey() const { return m_sshKey; }
+    GIT_CONN_TYPE GetConnType() const { return m_connType; }
+
+    void SetUsername( const wxString& aUsername ) { m_username = aUsername; }
+    void SetPassword( const wxString& aPassword ) { m_password = aPassword; }
+    void SetSSHKey( const wxString& aSSHKey ) { m_sshKey = aSSHKey; }
+
+    void SetConnType( GIT_CONN_TYPE aConnType ) { m_connType = aConnType; }
+    void SetConnType( unsigned aConnType )
+    {
+        if( aConnType < static_cast<unsigned>( GIT_CONN_TYPE::GIT_CONN_LAST ) )
+            m_connType = static_cast<GIT_CONN_TYPE>( aConnType );
+    }
+
+    // Holds a temporary variable that can be used by the authentication callback
+    // to remember which types of authentication have been tested so that we
+    // don't loop forever.
+    unsigned& TestedTypes() { return m_testedTypes; }
+
+    // Returns true if the repository has local commits that have not been pushed
+    bool HasLocalCommits() const;
+
+    // Returns true if the repository has a remote that can be pushed to pulled from
+    bool HasPushAndPullRemote() const;
+
+protected:
+    git_repository* m_repo;
+
+    GIT_CONN_TYPE m_connType;
+    wxString m_username;
+    wxString m_password;
+    wxString m_sshKey;
+
+    unsigned m_testedTypes;
+
+};
+
+
+extern "C" int progress_cb( const char* str, int len, void* data );
+extern "C" void clone_progress_cb( const char* str, size_t len, size_t total, void* data );
+extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload );
+extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
+                          void* aPayload );
+extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal,
+                                          size_t aBytes, void* aPayload );
+extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus,
+                                         void* aPayload );
+
+extern "C" int fetchhead_foreach_cb( const char*, const char*,
+                                     const git_oid* aOID, unsigned int aIsMerge, void* aPayload );
+extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
+                                unsigned int aAllowedTypes, void* aPayload );
+
+#endif // _GIT_COMMON_H_
\ No newline at end of file
diff --git a/common/git/kicad_git_errors.cpp b/common/git/kicad_git_errors.cpp
new file mode 100644
index 0000000000..97195b1b89
--- /dev/null
+++ b/common/git/kicad_git_errors.cpp
@@ -0,0 +1,66 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+namespace KIGIT_ERROR
+{
+    #undef _
+    #define _(a) a
+
+        // General errors
+    const char* const kInvalidRepository = _("Invalid repository.");
+    const char* const kCommitFailed = _("Failed to commit changes.");
+    const char* const kMergeFailed = _("Failed to merge branches.");
+
+    // Clone errors
+    const char* const kCloneFailed = _("Failed to clone repository.");
+    const char* const kRemoteNotFound = _("Remote repository not found.");
+    const char* const kAuthenticationFailed = _("Authentication failed for remote repository.");
+
+    // Branch errors
+    const char* const kBranchNotFound = _("Branch not found.");
+    const char* const kBranchCreationFailed = _("Failed to create branch.");
+    const char* const kBranchDeletionFailed = _("Failed to delete branch.");
+
+    // Checkout errors
+    const char* const kCheckoutFailed = _("Failed to perform checkout operation.");
+    const char* const kFileNotFoundInCheckout = _("File not found during checkout operation.");
+
+    // Conflict errors
+    const char* const kMergeConflict = _("Merge conflict encountered.");
+    const char* const kRebaseConflict = _("Rebase conflict encountered.");
+
+    // Pull/Push errors
+    const char* const kPullFailed = _("Failed to pull changes from remote repository.");
+    const char* const kPushFailed = _("Failed to push changes to remote repository.");
+    const char* const kNoUpstreamBranch = _("No upstream branch configured.");
+    const char* const kRemoteConnectionError = _("Failed to establish connection with remote repository.");
+
+    // Tag errors
+    const char* const kTagNotFound = _("Tag not found.");
+    const char* const kTagCreationFailed = _("Failed to create tag.");
+    const char* const kTagDeletionFailed = _("Failed to delete tag.");
+
+    const char* const kUnknownError = _("Unknown error.");
+    const char* const kNoError = _("No error.");
+
+}
\ No newline at end of file
diff --git a/common/git/kicad_git_errors.h b/common/git/kicad_git_errors.h
new file mode 100644
index 0000000000..2846dbfb1b
--- /dev/null
+++ b/common/git/kicad_git_errors.h
@@ -0,0 +1,81 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef KICAD_GIT_ERRORS_H
+#define KICAD_GIT_ERRORS_H
+
+#include <vector>
+
+#include <wx/translation.h>
+
+class KIGIT_ERRORS
+{
+public:
+
+    KIGIT_ERRORS() = default;
+
+    const std::vector<wxString>& GetErrorStrings() const
+    {
+        return m_errorStrings;
+    }
+
+    const wxString& PeekErrorString() const
+    {
+        if( m_errorStrings.empty() )
+            return _( "No error" );
+        else
+            return m_errorStrings.back();
+    }
+
+    wxString GetErrorString()
+    {
+        if( m_errorStrings.empty() )
+            return _( "No error" );
+
+        const wxString errorString( m_errorStrings.back() );
+        m_errorStrings.pop_back();
+        return errorString;
+    }
+
+    void AddErrorString( const wxString aErrorString )
+    {
+        m_errorStrings.emplace_back( aErrorString );
+    }
+
+    void AddErrorString( const std::string aErrorString )
+    {
+        m_errorStrings.emplace_back( aErrorString );
+    }
+
+    void ClearErrorStrings()
+    {
+        m_errorStrings.clear();
+    }
+
+private:
+
+    std::vector<wxString> m_errorStrings;
+
+};
+
+#endif // KICAD_GIT_ERRORS_H
diff --git a/common/project/project_local_settings.cpp b/common/project/project_local_settings.cpp
index 0b34328953..29ff3f359c 100644
--- a/common/project/project_local_settings.cpp
+++ b/common/project/project_local_settings.cpp
@@ -177,6 +177,18 @@ PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxStrin
                            ZONE_DISPLAY_MODE::SHOW_FILLED, ZONE_DISPLAY_MODE::SHOW_FILLED,
                            ZONE_DISPLAY_MODE::SHOW_TRIANGULATION ) );
 
+    m_params.emplace_back( new PARAM<wxString>( "git.repo_username",
+            &m_GitRepoUsername, "" ) );
+
+    m_params.emplace_back( new PARAM<wxString>( "git.repo_password",
+            &m_GitRepoPassword, "" ) );
+
+    m_params.emplace_back( new PARAM<wxString>( "git.repo_type",
+            &m_GitRepoType, "" ) );
+
+    m_params.emplace_back( new PARAM<wxString>( "git.ssh_key",
+            &m_GitSSHKey, "" ) );
+
     m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "project.files",
             [&]() -> nlohmann::json
             {
diff --git a/common/settings/common_settings.cpp b/common/settings/common_settings.cpp
index 527dbe581a..7e89063095 100644
--- a/common/settings/common_settings.cpp
+++ b/common/settings/common_settings.cpp
@@ -347,6 +347,61 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
     m_params.emplace_back( new PARAM<int>( "package_manager.sash_pos",
             &m_PackageManager.sash_pos, 380 ) );
 
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "git.repositories",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json ret = {};
+
+                for( const GIT_REPOSITORY& repo : m_Git.repositories )
+                {
+                    nlohmann::json repoJson = {};
+
+                    repoJson["name"] = repo.name;
+                    repoJson["path"] = repo.path;
+                    repoJson["authType"] = repo.authType;
+                    repoJson["username"] = repo.username;
+                    repoJson["ssh_path"] = repo.ssh_path;
+                    repoJson["active"] = repo.active;
+
+                    ret.push_back( repoJson );
+                }
+
+                return ret;
+            },
+            [&]( const nlohmann::json& aJson )
+            {
+                if( !aJson.is_array() )
+                    return;
+
+                m_Git.repositories.clear();
+
+                for( const auto& repoJson : aJson )
+                {
+                    GIT_REPOSITORY repo;
+
+                    repo.name = repoJson["name"].get<wxString>();
+                    repo.path = repoJson["path"].get<wxString>();
+                    repo.authType = repoJson["authType"].get<wxString>();
+                    repo.username = repoJson["username"].get<wxString>();
+                    repo.ssh_path = repoJson["ssh_path"].get<wxString>();
+                    repo.active = repoJson["active"].get<bool>();
+
+                    m_Git.repositories.push_back( repo );
+                }
+            },
+            {} ) );
+
+    m_params.emplace_back( new PARAM<wxString>( "git.authorName",
+            &m_Git.authorName, wxS( "" ) ) );
+
+    m_params.emplace_back( new PARAM<wxString>( "git.authorEmail",
+            &m_Git.authorEmail, wxS( "" ) ) );
+
+    m_params.emplace_back( new PARAM<bool>( "git.useDefaultAuthor",
+            &m_Git.useDefaultAuthor, true ) );
+
+
+
     registerMigration( 0, 1, std::bind( &COMMON_SETTINGS::migrateSchema0to1, this ) );
     registerMigration( 1, 2, std::bind( &COMMON_SETTINGS::migrateSchema1to2, this ) );
     registerMigration( 2, 3, std::bind( &COMMON_SETTINGS::migrateSchema2to3, this ) );
diff --git a/common/single_top.cpp b/common/single_top.cpp
index d045de79f7..63c6d19794 100644
--- a/common/single_top.cpp
+++ b/common/single_top.cpp
@@ -52,6 +52,8 @@
 #include <kiplatform/app.h>
 #include <kiplatform/environment.h>
 
+#include <git2.h>
+
 #ifdef KICAD_USE_SENTRY
 #include <sentry.h>
 #endif
@@ -73,6 +75,7 @@ static struct PGM_SINGLE_TOP : public PGM_BASE
 
     void OnPgmExit()
     {
+
         Kiway.OnKiwayEnd();
 
         if( m_settings_manager && m_settings_manager->IsOK() )
@@ -84,6 +87,7 @@ static struct PGM_SINGLE_TOP : public PGM_BASE
         // Destroy everything in PGM_BASE, especially wxSingleInstanceCheckerImpl
         // earlier than wxApp and earlier than static destruction would.
         PGM_BASE::Destroy();
+        git_libgit2_shutdown();
     }
 
     void MacOpenFile( const wxString& aFileName )   override
@@ -298,6 +302,9 @@ bool PGM_SINGLE_TOP::OnPgmInit()
     }
 #endif
 
+    // Initialize the git library before trying to initialize individual programs
+    git_libgit2_init();
+
     // Not all kicad applications use the python stuff. skip python init
     // for these apps.
     bool skip_python_initialization = false;
diff --git a/eeschema/lib_field.cpp b/eeschema/lib_field.cpp
index 0d432c9867..1ff99ee776 100644
--- a/eeschema/lib_field.cpp
+++ b/eeschema/lib_field.cpp
@@ -589,6 +589,58 @@ bool LIB_FIELD::IsMandatory() const
 }
 
 
+bool LIB_FIELD::operator==( const LIB_ITEM& aItem ) const
+{
+    if( aItem.Type() != LIB_FIELD_T )
+        return false;
+
+    const LIB_FIELD& field = static_cast<const LIB_FIELD&>( aItem );
+
+    if( m_id != field.m_id )
+        return false;
+
+    if( m_name != field.m_name )
+        return false;
+
+    if( m_parent->m_Uuid != aItem.GetParent()->m_Uuid )
+        return false;
+
+    if( m_id < MANDATORY_FIELDS )
+        return true;
+
+    if( m_Uuid == field.m_Uuid )
+        return true;
+
+    return EDA_TEXT::operator==( field );
+}
+
+
+double LIB_FIELD::Similarity( const LIB_ITEM& aItem ) const
+{
+    if( aItem.Type() != LIB_FIELD_T )
+        return 0.0;
+
+    const LIB_FIELD& field = static_cast<const LIB_FIELD&>( aItem );
+
+    if( m_id != field.m_id && m_id < MANDATORY_FIELDS )
+        return 0.0;
+
+    if( m_parent->m_Uuid != aItem.GetParent()->m_Uuid )
+        return 0.0;
+
+    if( m_id < MANDATORY_FIELDS )
+        return 1.0;
+
+    if( m_Uuid == field.m_Uuid )
+        return 1.0;
+
+    double similarity = SimilarityBase( field );
+
+    similarity *= EDA_TEXT::Similarity( field );
+
+    return similarity;
+}
+
 static struct LIB_FIELD_DESC
 {
     LIB_FIELD_DESC()
diff --git a/eeschema/lib_field.h b/eeschema/lib_field.h
index 03e5fa1c0c..2f3a510151 100644
--- a/eeschema/lib_field.h
+++ b/eeschema/lib_field.h
@@ -198,6 +198,10 @@ public:
     bool ShowInChooser() const { return m_showInChooser; }
     void SetShowInChooser( bool aShow = true ) { m_showInChooser = aShow; }
 
+    double Similarity( const LIB_ITEM& aItem ) const override;
+
+    bool operator==( const LIB_ITEM& aItem ) const override;
+
 private:
 
     /**
diff --git a/eeschema/lib_item.cpp b/eeschema/lib_item.cpp
index 4682b36f15..82a3f7d8fd 100644
--- a/eeschema/lib_item.cpp
+++ b/eeschema/lib_item.cpp
@@ -168,5 +168,3 @@ void LIB_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
     aLayers[1]  = LAYER_DEVICE_BACKGROUND;
     aLayers[2]  = LAYER_SELECTION_SHADOWS;
 }
-
-
diff --git a/eeschema/lib_item.h b/eeschema/lib_item.h
index 3f61330c70..6c9be0b960 100644
--- a/eeschema/lib_item.h
+++ b/eeschema/lib_item.h
@@ -204,6 +204,35 @@ public:
         return (LIB_SYMBOL*) m_parent;
     }
 
+    /**
+     * Return a measure of how likely the other object is to represent the same
+     * object.  The scale runs from 0.0 (definitely different objects) to 1.0 (same)
+     *
+     * This is a pure virtual function.  Derived classes must implement this.
+    */
+    virtual double Similarity( const LIB_ITEM& aItem ) const = 0;
+
+    /**
+     * Calculate the boilerplate similarity for all LIB_ITEMs without
+     * preventing the use above of a pure virtual function that catches at compile
+     * time when a new object has not been fully implemented
+    */
+    double SimilarityBase( const LIB_ITEM& aItem ) const
+    {
+        double similarity = 1.0;
+
+        if( m_unit != aItem.m_unit )
+            similarity *= 0.9;
+
+        if( m_convert != aItem.m_convert )
+            similarity *= 0.9;
+
+        if( m_private != aItem.m_private )
+            similarity *= 0.9;
+
+        return similarity;
+    }
+
     void ViewGetLayers( int aLayers[], int& aCount ) const override;
 
     bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override
@@ -237,7 +266,7 @@ public:
      * @param aOther Object to test against.
      * @return True if object is identical to this object.
      */
-    bool operator==( const LIB_ITEM& aOther ) const;
+    virtual bool operator==( const LIB_ITEM& aOther ) const;
     bool operator==( const LIB_ITEM* aOther ) const
     {
         return *this == *aOther;
diff --git a/eeschema/lib_pin.cpp b/eeschema/lib_pin.cpp
index e2eb5f7ad1..2b829239b6 100644
--- a/eeschema/lib_pin.cpp
+++ b/eeschema/lib_pin.cpp
@@ -1453,6 +1453,123 @@ wxString LIB_PIN::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const
 }
 
 
+bool LIB_PIN::operator==( const LIB_ITEM& aOther ) const
+{
+    if( aOther.Type() != LIB_PIN_T )
+        return false;
+
+    const LIB_PIN* other = static_cast<const LIB_PIN*>( &aOther );
+
+    if( m_name != other->m_name )
+        return false;
+
+    if( m_number != other->m_number )
+        return false;
+
+    if( m_position != other->m_position )
+        return false;
+
+    if( m_length != other->m_length )
+        return false;
+
+    if( m_orientation != other->m_orientation )
+        return false;
+
+    if( m_shape != other->m_shape )
+        return false;
+
+    if( m_type != other->m_type )
+        return false;
+
+    if( m_attributes != other->m_attributes )
+        return false;
+
+    if( m_numTextSize != other->m_numTextSize )
+        return false;
+
+    if( m_nameTextSize != other->m_nameTextSize )
+        return false;
+
+    if( m_alternates.size() != other->m_alternates.size() )
+        return false;
+
+    auto lhsItem = m_alternates.begin();
+    auto rhsItem = other->m_alternates.begin();
+
+    while( lhsItem != m_alternates.end() )
+    {
+        if( rhsItem == other->m_alternates.end() )
+            return false;
+
+        const ALT& lhsAlt = lhsItem->second;
+        const ALT& rhsAlt = rhsItem->second;
+
+        if( lhsAlt.m_Name != rhsAlt.m_Name )
+            return false;
+
+        if( lhsAlt.m_Type != rhsAlt.m_Type )
+            return false;
+
+        if( lhsAlt.m_Shape != rhsAlt.m_Shape )
+            return false;
+
+        ++lhsItem;
+        ++rhsItem;
+    }
+
+    return rhsItem == other->m_alternates.end();
+}
+
+
+double LIB_PIN::Similarity( const LIB_ITEM& aOther ) const
+{
+    if( aOther.m_Uuid == m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != LIB_PIN_T )
+        return 0.0;
+
+    const LIB_PIN* other = static_cast<const LIB_PIN*>( &aOther );
+
+    double similarity = SimilarityBase( aOther );
+
+    if( m_name != other->m_name )
+        similarity *= 0.9;
+
+    if( m_number != other->m_number )
+        similarity *= 0.9;
+
+    if( m_position != other->m_position )
+        similarity *= 0.9;
+
+    if( m_length != other->m_length )
+        similarity *= 0.9;
+
+    if( m_orientation != other->m_orientation )
+        similarity *= 0.9;
+
+    if( m_shape != other->m_shape )
+        similarity *= 0.9;
+
+    if( m_type != other->m_type )
+        similarity *= 0.9;
+
+    if( m_attributes != other->m_attributes )
+        similarity *= 0.9;
+
+    if( m_numTextSize != other->m_numTextSize )
+        similarity *= 0.9;
+
+    if( m_nameTextSize != other->m_nameTextSize )
+        similarity *= 0.9;
+
+    if( m_alternates.size() != other->m_alternates.size() )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 std::ostream& LIB_PIN::operator<<( std::ostream& aStream )
 {
     aStream << "LIB_PIN:" << std::endl
diff --git a/eeschema/lib_pin.h b/eeschema/lib_pin.h
index 8dbd67c90e..c12a1ad50c 100644
--- a/eeschema/lib_pin.h
+++ b/eeschema/lib_pin.h
@@ -265,6 +265,13 @@ public:
      */
     static const wxString GetCanonicalElectricalTypeName( ELECTRICAL_PINTYPE aType );
 
+    double Similarity( const LIB_ITEM& aItem ) const override;
+
+    bool operator==( const LIB_ITEM& aItem ) const override;
+    bool operator!=( const LIB_ITEM& aItem ) const { return !operator==( aItem ); }
+    bool operator<( const LIB_PIN& aRhs ) const { return compare( aRhs, EQUALITY ) < 0; }
+    bool operator>( const LIB_PIN& aRhs ) const { return compare( aRhs, EQUALITY ) > 0; }
+
 protected:
     struct EXTENTS_CACHE
     {
@@ -300,10 +307,6 @@ protected:
      */
     void printPinElectricalTypeName( const RENDER_SETTINGS* aSettings, VECTOR2I& aPosition,
                                      PIN_ORIENTATION aOrientation, bool aDimmed );
-
-    bool operator==( const LIB_PIN& aRhs ) const { return compare( aRhs, EQUALITY ) == 0; }
-    bool operator<( const LIB_PIN& aRhs ) const { return compare( aRhs, EQUALITY ) < 0; }
-    bool operator>( const LIB_PIN& aRhs ) const { return compare( aRhs, EQUALITY ) > 0; }
     std::ostream& operator<<( std::ostream& aStream );
 
 private:
diff --git a/eeschema/lib_shape.cpp b/eeschema/lib_shape.cpp
index e28aa78549..d5b9cd06ca 100644
--- a/eeschema/lib_shape.cpp
+++ b/eeschema/lib_shape.cpp
@@ -518,6 +518,33 @@ void LIB_SHAPE::AddPoint( const VECTOR2I& aPosition )
 }
 
 
+bool LIB_SHAPE::operator==( const LIB_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const LIB_SHAPE& other = static_cast<const LIB_SHAPE&>( aOther );
+
+    return LIB_ITEM::operator==( aOther ) && EDA_SHAPE::operator==( other );
+}
+
+
+double LIB_SHAPE::Similarity( const LIB_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const LIB_SHAPE& other = static_cast<const LIB_SHAPE&>( aOther );
+
+    double similarity = SimilarityBase( other );
+
+    similarity *= EDA_SHAPE::Similarity( other );
+
+    return similarity;
+}
 
 
 void LIB_SHAPE::ViewGetLayers( int aLayers[], int& aCount ) const
diff --git a/eeschema/lib_shape.h b/eeschema/lib_shape.h
index 9ae7065b3d..3d74ed3da4 100644
--- a/eeschema/lib_shape.h
+++ b/eeschema/lib_shape.h
@@ -120,6 +120,10 @@ public:
 
     void ViewGetLayers( int aLayers[], int& aCount ) const override;
 
+    double Similarity( const LIB_ITEM& aOther ) const override;
+
+    bool operator==( const LIB_ITEM& aOther ) const override;
+
 private:
     /**
      * @copydoc LIB_ITEM::compare()
diff --git a/eeschema/lib_symbol.cpp b/eeschema/lib_symbol.cpp
index 857df85513..706a83c9db 100644
--- a/eeschema/lib_symbol.cpp
+++ b/eeschema/lib_symbol.cpp
@@ -1912,3 +1912,139 @@ std::vector<struct LIB_SYMBOL_UNIT> LIB_SYMBOL::GetUniqueUnits()
 
     return uniqueUnits;
 }
+
+
+bool LIB_SYMBOL::operator==( const LIB_SYMBOL& aOther ) const
+{
+    if( m_libId != aOther.m_libId )
+        return false;
+
+    if( m_excludedFromBoard != aOther.m_excludedFromBoard )
+        return false;
+
+    if( m_excludedFromBOM != aOther.m_excludedFromBOM )
+        return false;
+
+    if( m_excludedFromSim != aOther.m_excludedFromSim )
+        return false;
+
+    if( m_flags != aOther.m_flags )
+        return false;
+
+    if( m_unitCount != aOther.m_unitCount )
+        return false;
+
+    if( m_pinNameOffset != aOther.m_pinNameOffset )
+        return false;
+
+    if( m_showPinNames != aOther.m_showPinNames )
+        return false;
+
+    if( m_showPinNumbers != aOther.m_showPinNumbers )
+        return false;
+
+    if( m_drawings.size() != aOther.m_drawings.size() )
+        return false;
+
+    for( auto it1 = m_drawings.begin(), it2 = aOther.m_drawings.begin();
+         it1 != m_drawings.end(); ++it1, ++it2 )
+    {
+        if( !( *it1 == *it2 ) )
+            return false;
+    }
+
+    const LIB_PINS thisPinList = GetAllLibPins();
+    const LIB_PINS otherPinList = aOther.GetAllLibPins();
+
+    if( thisPinList.size() != otherPinList.size() )
+        return false;
+
+    for( auto it1 = thisPinList.begin(), it2 = otherPinList.begin();
+         it1 != thisPinList.end(); ++it1, ++it2 )
+    {
+        if( !( **it1 == **it2 ) )
+            return false;
+    }
+    for( size_t ii = 0; ii < thisPinList.size(); ++ii )
+    {
+        if( !( *thisPinList[ii] == *otherPinList[ii] ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double LIB_SYMBOL::Similarity( const LIB_SYMBOL& aOther ) const
+{
+    double similarity = 0.0;
+    int    totalItems = 0;
+
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    for( const LIB_ITEM& item : m_drawings )
+    {
+        totalItems += 1;
+        double max_similarity = 0.0;
+
+        for( const LIB_ITEM& otherItem : aOther.m_drawings )
+        {
+            double temp_similarity = item.Similarity( otherItem );
+            max_similarity = std::max( max_similarity, temp_similarity );
+
+            if( max_similarity == 1.0 )
+                break;
+        }
+
+        similarity += max_similarity;
+    }
+
+    for( const LIB_PIN* pin : GetAllLibPins() )
+    {
+        totalItems += 1;
+        double max_similarity = 0.0;
+
+        for( const LIB_PIN* otherPin : aOther.GetAllLibPins() )
+        {
+            double temp_similarity = pin->Similarity( *otherPin );
+            max_similarity = std::max( max_similarity, temp_similarity );
+
+            if( max_similarity == 1.0 )
+                break;
+        }
+
+        similarity += max_similarity;
+    }
+
+    if( totalItems == 0 )
+        similarity = 0.0;
+    else
+        similarity /= totalItems;
+
+    if( m_excludedFromBoard != aOther.m_excludedFromBoard )
+        similarity *= 0.9;
+
+    if( m_excludedFromBOM != aOther.m_excludedFromBOM )
+        similarity *= 0.9;
+
+    if( m_excludedFromSim != aOther.m_excludedFromSim )
+        similarity *= 0.9;
+
+    if( m_flags != aOther.m_flags )
+        similarity *= 0.9;
+
+    if( m_unitCount != aOther.m_unitCount )
+        similarity *= 0.5;
+
+    if( m_pinNameOffset != aOther.m_pinNameOffset )
+        similarity *= 0.9;
+
+    if( m_showPinNames != aOther.m_showPinNames )
+        similarity *= 0.9;
+
+    if( m_showPinNumbers != aOther.m_showPinNumbers )
+        similarity *= 0.9;
+
+    return similarity;
+}
\ No newline at end of file
diff --git a/eeschema/lib_symbol.h b/eeschema/lib_symbol.h
index 7b100623f5..db1223386d 100644
--- a/eeschema/lib_symbol.h
+++ b/eeschema/lib_symbol.h
@@ -711,11 +711,7 @@ public:
                  REPORTER* aReporter = nullptr ) const;
 
     bool operator==( const LIB_SYMBOL* aSymbol ) const { return this == aSymbol; }
-    bool operator==( const LIB_SYMBOL& aSymbol ) const
-    {
-        return Compare( aSymbol, LIB_ITEM::COMPARE_FLAGS::EQUALITY ) == 0;
-    }
-
+    bool operator==( const LIB_SYMBOL& aSymbol ) const;
     bool operator!=( const LIB_SYMBOL& aSymbol ) const
     {
         return Compare( aSymbol, LIB_ITEM::COMPARE_FLAGS::EQUALITY ) != 0;
@@ -762,6 +758,13 @@ public:
      */
     std::vector<LIB_ITEM*> GetUnitDrawItems( int aUnit, int aConvert );
 
+    /**
+     * Return a measure of similarity between this symbol and \a aSymbol.
+     * @param aSymbol is the symbol to compare to.
+     *
+     * @return a measure of similarity from 1.0 (identical) to 0.0 (no similarity).
+    */
+    double Similarity( const LIB_SYMBOL& aSymbol ) const;
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/eeschema/lib_text.cpp b/eeschema/lib_text.cpp
index a784cef711..5df27fedfc 100644
--- a/eeschema/lib_text.cpp
+++ b/eeschema/lib_text.cpp
@@ -500,6 +500,33 @@ void LIB_TEXT::CalcEdit( const VECTOR2I& aPosition )
 }
 
 
+bool LIB_TEXT::operator==( const LIB_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const LIB_TEXT& other = static_cast<const LIB_TEXT&>( aOther );
+
+    return LIB_ITEM::operator==( aOther ) && EDA_TEXT::operator==( other );
+}
+
+
+double LIB_TEXT::Similarity( const LIB_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const LIB_TEXT& other = static_cast<const LIB_TEXT&>( aOther );
+
+    double similarity = SimilarityBase( other );
+    similarity *= EDA_TEXT::Similarity( other );
+
+    return similarity;
+}
+
 static struct LIB_TEXT_DESC
 {
     LIB_TEXT_DESC()
diff --git a/eeschema/lib_text.h b/eeschema/lib_text.h
index ea9622324c..65fd6970a4 100644
--- a/eeschema/lib_text.h
+++ b/eeschema/lib_text.h
@@ -113,6 +113,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const LIB_ITEM& aOther ) const override;
+
+    bool operator==( const LIB_ITEM& aOther ) const override;
+
 private:
     /**
      * @copydoc LIB_ITEM::compare()
diff --git a/eeschema/lib_textbox.cpp b/eeschema/lib_textbox.cpp
index d162cd7257..55b5a6ad93 100644
--- a/eeschema/lib_textbox.cpp
+++ b/eeschema/lib_textbox.cpp
@@ -501,6 +501,35 @@ void LIB_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL
 }
 
 
+bool LIB_TEXTBOX::operator==( const LIB_ITEM& aOther ) const
+{
+    if( aOther.Type() != LIB_TEXTBOX_T )
+        return false;
+
+    const LIB_TEXTBOX& other = static_cast<const LIB_TEXTBOX&>( aOther );
+
+    return LIB_SHAPE::operator==( other ) && EDA_TEXT::operator==( other );
+}
+
+
+double LIB_TEXTBOX::Similarity( const LIB_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != LIB_TEXTBOX_T )
+        return 0.0;
+
+    const LIB_TEXTBOX& other = static_cast<const LIB_TEXTBOX&>( aOther );
+
+    double similarity = SimilarityBase( other );
+    similarity *= LIB_SHAPE::Similarity( other );
+    similarity *= EDA_TEXT::Similarity( other );
+
+    return similarity;
+}
+
+
 void LIB_TEXTBOX::ViewGetLayers( int aLayers[], int& aCount ) const
 {
     aCount     = 3;
diff --git a/eeschema/lib_textbox.h b/eeschema/lib_textbox.h
index be73336127..4df451e445 100644
--- a/eeschema/lib_textbox.h
+++ b/eeschema/lib_textbox.h
@@ -93,6 +93,10 @@ public:
 
     void ViewGetLayers( int aLayers[], int& aCount ) const override;
 
+    double Similarity( const LIB_ITEM& aOther ) const override;
+
+    bool operator==( const LIB_ITEM& aOther ) const override;
+
 protected:
         KIFONT::FONT* getDrawFont() const override;
 
diff --git a/eeschema/sch_bitmap.cpp b/eeschema/sch_bitmap.cpp
index 2dc8898f79..de9380e492 100644
--- a/eeschema/sch_bitmap.cpp
+++ b/eeschema/sch_bitmap.cpp
@@ -249,3 +249,45 @@ static struct SCH_BITMAP_DESC
         propMgr.InheritsAfter( TYPE_HASH( SCH_BITMAP ), TYPE_HASH( SCH_ITEM ) );
     }
 } _SCH_BITMAP_DESC;
+
+
+bool SCH_BITMAP::operator==( const SCH_ITEM& aItem ) const
+{
+    if( Type() != aItem.Type() )
+        return false;
+
+    const SCH_BITMAP* bitmap = static_cast<const SCH_BITMAP*>( &aItem );
+
+    if( GetPosition() != bitmap->GetPosition() )
+        return false;
+
+    if( GetSize() != bitmap->GetSize() )
+        return false;
+
+    if( GetImage() != bitmap->GetImage() )
+        return false;
+
+    return true;
+}
+
+
+double SCH_BITMAP::Similarity( const SCH_ITEM& aItem ) const
+{
+    if( Type() != aItem.Type() )
+        return 0.0;
+
+    if( m_Uuid == aItem.m_Uuid )
+        return 1.0;
+
+    const SCH_BITMAP* bitmap = static_cast<const SCH_BITMAP*>( &aItem );
+
+    if( GetImage() != bitmap->GetImage() )
+        return 0.0;
+
+    // If it is the same image but a different UUID and a different size,
+    // then it _might be different_.
+    if( GetSize() != bitmap->GetSize() )
+        return 0.5;
+
+    return 1.0;
+}
\ No newline at end of file
diff --git a/eeschema/sch_bitmap.h b/eeschema/sch_bitmap.h
index 678f1165cb..9fa782cfb6 100644
--- a/eeschema/sch_bitmap.h
+++ b/eeschema/sch_bitmap.h
@@ -154,6 +154,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/sch_bus_entry.cpp b/eeschema/sch_bus_entry.cpp
index 24a692f592..41dbb4a799 100644
--- a/eeschema/sch_bus_entry.cpp
+++ b/eeschema/sch_bus_entry.cpp
@@ -577,6 +577,45 @@ bool SCH_BUS_WIRE_ENTRY::ConnectionPropagatesTo( const EDA_ITEM* aItem ) const
     return true;
 }
 
+bool SCH_BUS_ENTRY_BASE::operator==( const SCH_ITEM& aItem ) const
+{
+    if( Type() != aItem.Type() )
+        return false;
+
+    const SCH_BUS_ENTRY_BASE* symbol = static_cast<const SCH_BUS_ENTRY_BASE*>( &aItem );
+
+    if( GetLayer() != symbol->GetLayer() )
+        return false;
+
+    if( GetPosition() != symbol->GetPosition() )
+        return false;
+
+    if( GetEnd() != symbol->GetEnd() )
+        return false;
+
+    return true;
+}
+
+
+double SCH_BUS_ENTRY_BASE::Similarity( const SCH_ITEM& aItem ) const
+{
+    if( aItem.Type() != Type() )
+        return 0.0;
+
+    if( m_Uuid == aItem.m_Uuid )
+        return 1.0;
+
+    const SCH_BUS_ENTRY_BASE& other = static_cast<const SCH_BUS_ENTRY_BASE&>( aItem );
+
+    if( GetLayer() != other.GetLayer() )
+        return 0.0;
+
+    if( GetPosition() != other.GetPosition() )
+        return 0.0;
+
+    return 1.0;
+}
+
 
 static struct SCH_BUS_ENTRY_DESC
 {
diff --git a/eeschema/sch_bus_entry.h b/eeschema/sch_bus_entry.h
index 6f105049aa..9322aee6b5 100644
--- a/eeschema/sch_bus_entry.h
+++ b/eeschema/sch_bus_entry.h
@@ -128,6 +128,10 @@ public:
 
     bool operator <( const SCH_ITEM& aItem ) const override;
 
+    double Similarity( const SCH_ITEM& aItem ) const override;
+
+    bool operator==( const SCH_ITEM& aItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp
index 760f29215b..49df6b3160 100644
--- a/eeschema/sch_field.cpp
+++ b/eeschema/sch_field.cpp
@@ -1294,6 +1294,74 @@ bool SCH_FIELD::operator <( const SCH_ITEM& aItem ) const
     return GetName() < field->GetName();
 }
 
+bool SCH_FIELD::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_FIELD& field = static_cast<const SCH_FIELD&>( aOther );
+
+    if( GetId() != field.GetId() )
+        return false;
+
+    if( GetPosition() != field.GetPosition() )
+        return false;
+
+    if( IsNamedVariable() != field.IsNamedVariable() )
+        return false;
+
+    if( IsNameShown() != field.IsNameShown() )
+        return false;
+
+    if( CanAutoplace() != field.CanAutoplace() )
+        return false;
+
+    if( GetText() != field.GetText() )
+        return false;
+
+    return true;
+}
+
+
+double SCH_FIELD::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    const SCH_FIELD& field = static_cast<const SCH_FIELD&>( aOther );
+
+    double retval = 0.99; // The UUIDs are different, so we start with non-identity
+
+    if( GetId() != field.GetId() )
+    {
+        // We don't allow swapping of mandatory fields, so these cannot be the same item
+        if( GetId() < MANDATORY_FIELDS || field.GetId() < MANDATORY_FIELDS )
+            return 0.0;
+        else
+            retval *= 0.5;
+    }
+
+    if( GetPosition() != field.GetPosition() )
+        retval *= 0.5;
+
+    if( IsNamedVariable() != field.IsNamedVariable() )
+        retval *= 0.5;
+
+    if( IsNameShown() != field.IsNameShown() )
+        retval *= 0.5;
+
+    if( CanAutoplace() != field.CanAutoplace() )
+        retval *= 0.5;
+
+    if( GetText() != field.GetText() )
+        retval *= Levenshtein( field );
+
+    return 1.0;
+}
+
 
 static struct SCH_FIELD_DESC
 {
diff --git a/eeschema/sch_field.h b/eeschema/sch_field.h
index 57d75e99d1..b6aa115373 100644
--- a/eeschema/sch_field.h
+++ b/eeschema/sch_field.h
@@ -275,6 +275,10 @@ public:
 
     bool operator <( const SCH_ITEM& aItem ) const override;
 
+    double Similarity( const SCH_ITEM& aItem ) const override;
+
+    bool operator==( const SCH_ITEM& aItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/eeschema/sch_item.h b/eeschema/sch_item.h
index f4ea44816f..d8e6b31cd4 100644
--- a/eeschema/sch_item.h
+++ b/eeschema/sch_item.h
@@ -279,6 +279,15 @@ public:
 
     bool RenderAsBitmap( double aWorldScale ) const override;
 
+    /**
+     * Return a measure of how likely the other object is to represent the same
+     * object.  The scale runs from 0.0 (definitely different objects) to 1.0 (same)
+     *
+     * This is a pure virtual function.  Derived classes must implement this.
+    */
+    virtual double Similarity( const SCH_ITEM& aItem ) const = 0;
+    virtual bool operator==( const SCH_ITEM& aItem ) const = 0;
+
     /**
      * Print a schematic item.
      *
diff --git a/eeschema/sch_junction.cpp b/eeschema/sch_junction.cpp
index 8fe4a37959..febd2ca444 100644
--- a/eeschema/sch_junction.cpp
+++ b/eeschema/sch_junction.cpp
@@ -307,6 +307,51 @@ void SCH_JUNCTION::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANE
 }
 
 
+bool SCH_JUNCTION::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_JUNCTION& other = static_cast<const SCH_JUNCTION&>( aOther );
+
+    if( m_pos != other.m_pos )
+        return false;
+
+    if( m_diameter != other.m_diameter )
+        return false;
+
+    if( m_color != other.m_color )
+        return false;
+
+    return true;
+}
+
+
+double SCH_JUNCTION::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const SCH_JUNCTION& other = static_cast<const SCH_JUNCTION&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_pos != other.m_pos )
+        similarity *= 0.9;
+
+    if( m_diameter != other.m_diameter )
+        similarity *= 0.9;
+
+    if( m_color != other.m_color )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 static struct SCH_JUNCTION_DESC
 {
     SCH_JUNCTION_DESC()
diff --git a/eeschema/sch_junction.h b/eeschema/sch_junction.h
index 0a23e1cb70..1b229288ee 100644
--- a/eeschema/sch_junction.h
+++ b/eeschema/sch_junction.h
@@ -129,6 +129,10 @@ public:
 
     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp
index 29a92f4227..8f63a5c205 100644
--- a/eeschema/sch_label.cpp
+++ b/eeschema/sch_label.cpp
@@ -579,6 +579,69 @@ bool SCH_LABEL_BASE::IncrementLabel( int aIncrement )
 }
 
 
+bool SCH_LABEL_BASE::operator==( const SCH_ITEM& aOther ) const
+{
+    const SCH_LABEL_BASE* other = dynamic_cast<const SCH_LABEL_BASE*>( &aOther );
+
+    if( !other )
+        return false;
+
+    if( m_shape != other->m_shape )
+        return false;
+
+    if( m_connectionType != other->m_connectionType )
+        return false;
+
+    if( m_fields.size() != other->m_fields.size() )
+        return false;
+
+    for( size_t ii = 0; ii < m_fields.size(); ++ii )
+    {
+        if( !( m_fields[ii] == other->m_fields[ii] ) )
+            return false;
+    }
+
+    return SCH_TEXT::operator==( aOther );
+}
+
+
+double SCH_LABEL_BASE::Similarity( const SCH_ITEM& aOther ) const
+{
+    const SCH_LABEL_BASE* other = dynamic_cast<const SCH_LABEL_BASE*>( &aOther );
+
+    if( !other )
+        return 0.0;
+
+    if( m_Uuid == other->m_Uuid )
+        return 1.0;
+
+    double similarity = SCH_TEXT::Similarity( aOther );
+
+    if( typeid( *this ) != typeid( aOther ) )
+        similarity *= 0.9;
+
+    if( m_shape == other->m_shape )
+        similarity *= 0.9;
+
+    if( m_connectionType == other->m_connectionType )
+        similarity *= 0.9;
+
+    for( size_t ii = 0; ii < m_fields.size(); ++ii )
+    {
+        if( ii >= other->m_fields.size() )
+            break;
+
+        similarity *= m_fields[ii].Similarity( other->m_fields[ii] );
+    }
+
+    int diff = std::abs( int( m_fields.size() ) - int( other->m_fields.size() ) );
+
+    similarity *= std::pow( 0.9, diff );
+
+    return similarity;
+}
+
+
 void SCH_LABEL_BASE::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
 {
     int margin = GetTextOffset() * 2;
diff --git a/eeschema/sch_label.h b/eeschema/sch_label.h
index 55f355c53f..ae139c4fb2 100644
--- a/eeschema/sch_label.h
+++ b/eeschema/sch_label.h
@@ -311,6 +311,10 @@ public:
      */
     virtual bool AutoRotateOnPlacementSupported() const = 0;
 
+    double Similarity( const SCH_ITEM& aItem ) const override;
+
+    bool operator==( const SCH_ITEM& aItem ) const override;
+
 protected:
     void cacheShownText() override;
 
diff --git a/eeschema/sch_line.cpp b/eeschema/sch_line.cpp
index 4cd7003c88..60dc411bf1 100644
--- a/eeschema/sch_line.cpp
+++ b/eeschema/sch_line.cpp
@@ -978,6 +978,69 @@ bool SCH_LINE::IsBus() const
 }
 
 
+bool SCH_LINE::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_LINE& other = static_cast<const SCH_LINE&>( aOther );
+
+    if( GetLayer() != other.GetLayer() )
+        return false;
+
+    if( m_start != other.m_start )
+        return false;
+
+    if( m_end != other.m_end )
+        return false;
+
+    if( m_stroke.GetWidth() != other.m_stroke.GetWidth() )
+        return false;
+
+    if( m_stroke.GetColor() != other.m_stroke.GetColor() )
+        return false;
+
+    if( m_stroke.GetPlotStyle() != other.m_stroke.GetPlotStyle() )
+        return false;
+
+    return true;
+}
+
+
+double SCH_LINE::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    const SCH_LINE& other = static_cast<const SCH_LINE&>( aOther );
+
+    if( GetLayer() != other.GetLayer() )
+        return 0.0;
+
+    double similarity = 1.0;
+
+    if( m_start != other.m_start )
+        similarity *= 0.9;
+
+    if( m_end != other.m_end )
+        similarity *= 0.9;
+
+    if( m_stroke.GetWidth() != other.m_stroke.GetWidth() )
+        similarity *= 0.9;
+
+    if( m_stroke.GetColor() != other.m_stroke.GetColor() )
+        similarity *= 0.9;
+
+    if( m_stroke.GetPlotStyle() != other.m_stroke.GetPlotStyle() )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 static struct SCH_LINE_DESC
 {
     SCH_LINE_DESC()
diff --git a/eeschema/sch_line.h b/eeschema/sch_line.h
index 5aad5046a2..2e1c6368bb 100644
--- a/eeschema/sch_line.h
+++ b/eeschema/sch_line.h
@@ -326,6 +326,10 @@ public:
      */
     bool IsBus() const;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 private:
     /**
      * @brief Recursively called function to travel through the connected wires and find a connected
diff --git a/eeschema/sch_marker.h b/eeschema/sch_marker.h
index 891738ac15..a3e9adf68c 100644
--- a/eeschema/sch_marker.h
+++ b/eeschema/sch_marker.h
@@ -120,6 +120,16 @@ public:
      */
     bool IsLegacyMarker() const { return m_isLegacyMarker; }
 
+    double Similarity( const SCH_ITEM& aOther ) const override
+    {
+        return 0.0;
+    }
+
+    bool operator==( const SCH_ITEM& aOther ) const override
+    {
+        return false;
+    }
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/sch_no_connect.cpp b/eeschema/sch_no_connect.cpp
index d92f2a0c84..0727798045 100644
--- a/eeschema/sch_no_connect.cpp
+++ b/eeschema/sch_no_connect.cpp
@@ -195,3 +195,34 @@ BITMAPS SCH_NO_CONNECT::GetMenuImage() const
 {
     return BITMAPS::noconn;
 }
+
+
+bool SCH_NO_CONNECT::operator==( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const SCH_NO_CONNECT* other = static_cast<const SCH_NO_CONNECT*>( &aOther );
+
+    if( m_pos != other->m_pos )
+        return false;
+
+    return true;
+}
+
+
+double SCH_NO_CONNECT::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const SCH_NO_CONNECT* other = static_cast<const SCH_NO_CONNECT*>( &aOther );
+
+    if( m_pos != other->m_pos )
+        return 0.0;
+
+    return 1.0;
+}
\ No newline at end of file
diff --git a/eeschema/sch_no_connect.h b/eeschema/sch_no_connect.h
index 6ad440c994..5d6d8d506a 100644
--- a/eeschema/sch_no_connect.h
+++ b/eeschema/sch_no_connect.h
@@ -109,6 +109,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/eeschema/sch_pin.cpp b/eeschema/sch_pin.cpp
index 445350e4b6..21b3877246 100644
--- a/eeschema/sch_pin.cpp
+++ b/eeschema/sch_pin.cpp
@@ -416,6 +416,43 @@ bool SCH_PIN::ConnectionPropagatesTo( const EDA_ITEM* aItem ) const
 }
 
 
+bool SCH_PIN::operator==( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != SCH_PIN_T )
+        return false;
+
+    const SCH_PIN& other = static_cast<const SCH_PIN&>( aOther );
+
+    if( m_number != other.m_number )
+        return false;
+
+    if( m_position != other.m_position )
+        return false;
+
+    return m_libPin == other.m_libPin;
+}
+
+
+double SCH_PIN::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != SCH_PIN_T )
+        return 0.0;
+
+    const SCH_PIN& other = static_cast<const SCH_PIN&>( aOther );
+
+    if( m_number != other.m_number )
+        return 0.0;
+
+    if( m_position != other.m_position )
+        return 0.0;
+
+    return m_libPin->Similarity( *other.m_libPin );
+}
+
+
 static struct SCH_PIN_DESC
 {
     SCH_PIN_DESC()
diff --git a/eeschema/sch_pin.h b/eeschema/sch_pin.h
index 2dc058874a..9867f95db8 100644
--- a/eeschema/sch_pin.h
+++ b/eeschema/sch_pin.h
@@ -164,6 +164,10 @@ public:
     const wxString& GetOperatingPoint() const { return m_operatingPoint; }
     void SetOperatingPoint( const wxString& aText ) { m_operatingPoint = aText; }
 
+    double Similarity( const SCH_ITEM& aItem ) const override;
+
+    bool operator==( const SCH_ITEM& aItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override {}
 #endif
diff --git a/eeschema/sch_shape.cpp b/eeschema/sch_shape.cpp
index 3ed7aa01dd..b93ebca00c 100644
--- a/eeschema/sch_shape.cpp
+++ b/eeschema/sch_shape.cpp
@@ -502,6 +502,30 @@ void SCH_SHAPE::AddPoint( const VECTOR2I& aPosition )
 }
 
 
+bool SCH_SHAPE::operator==( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const SCH_SHAPE& other = static_cast<const SCH_SHAPE&>( aOther );
+
+    return EDA_SHAPE::operator==( other );
+}
+
+
+double SCH_SHAPE::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const SCH_SHAPE& other = static_cast<const SCH_SHAPE&>( aOther );
+
+    double similarity = EDA_SHAPE::Similarity( other );
+
+    return similarity;
+}
+
+
 static struct SCH_SHAPE_DESC
 {
     SCH_SHAPE_DESC()
diff --git a/eeschema/sch_shape.h b/eeschema/sch_shape.h
index 6de173413c..9dc07004d9 100644
--- a/eeschema/sch_shape.h
+++ b/eeschema/sch_shape.h
@@ -107,6 +107,10 @@ public:
 
     void ViewGetLayers( int aLayers[], int& aCount ) const override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp
index fa0c138125..b45e3b99a1 100644
--- a/eeschema/sch_sheet.cpp
+++ b/eeschema/sch_sheet.cpp
@@ -1387,6 +1387,55 @@ int SCH_SHEET::ComparePageNum( const wxString& aPageNumberA, const wxString& aPa
 }
 
 
+bool SCH_SHEET::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_SHEET* other = static_cast<const SCH_SHEET*>( &aOther );
+
+    if( m_pos != other->m_pos )
+        return false;
+
+    if( m_size != other->m_size )
+        return false;
+
+    if( GetBorderColor() != other->GetBorderColor() )
+        return false;
+
+    if( GetBackgroundColor() != other->GetBackgroundColor() )
+        return false;
+
+    if( GetBorderWidth() != other->GetBorderWidth() )
+        return false;
+
+    if( GetFields().size() != other->GetFields().size() )
+        return false;
+
+    for( size_t i = 0; i < GetFields().size(); ++i )
+    {
+        if( !( GetFields()[i] == other->GetFields()[i] ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double SCH_SHEET::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    const SCH_SHEET* other = static_cast<const SCH_SHEET*>( &aOther );
+
+    if( m_screen->GetFileName() == other->m_screen->GetFileName() )
+        return 1.0;
+
+    return 0.0;
+}
+
+
 #if defined(DEBUG)
 
 void SCH_SHEET::Show( int nestLevel, std::ostream& os ) const
diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h
index 43e6b098c9..7991b23135 100644
--- a/eeschema/sch_sheet.h
+++ b/eeschema/sch_sheet.h
@@ -418,6 +418,10 @@ public:
      */
     static int ComparePageNum( const wxString& aPageNumberA, const wxString& aPageNumberB );
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/sch_sheet_path.cpp b/eeschema/sch_sheet_path.cpp
index 8bc75819ac..c90dcd46a4 100644
--- a/eeschema/sch_sheet_path.cpp
+++ b/eeschema/sch_sheet_path.cpp
@@ -80,6 +80,16 @@ public:
     void MirrorVertically( int aCenter ) override {}
     void Rotate( const VECTOR2I& aCenter ) override {}
 
+    double Similarity( const SCH_ITEM& aOther ) const override
+    {
+        return 0.0;
+    }
+
+    bool operator==( const SCH_ITEM& aOther ) const override
+    {
+        return false;
+    }
+
 #if defined(DEBUG)
     void Show( int , std::ostream&  ) const override {}
 #endif
diff --git a/eeschema/sch_sheet_pin.cpp b/eeschema/sch_sheet_pin.cpp
index 1dddb167f3..cdbeb2244a 100644
--- a/eeschema/sch_sheet_pin.cpp
+++ b/eeschema/sch_sheet_pin.cpp
@@ -356,6 +356,39 @@ bool SCH_SHEET_PIN::HitTest( const VECTOR2I& aPoint, int aAccuracy ) const
 }
 
 
+bool SCH_SHEET_PIN::operator==( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const SCH_SHEET_PIN* other = static_cast<const SCH_SHEET_PIN*>( &aOther );
+
+    return m_edge == other->m_edge && m_number == other->m_number
+           && SCH_HIERLABEL::operator==( aOther );
+}
+
+
+double SCH_SHEET_PIN::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const SCH_SHEET_PIN* other = static_cast<const SCH_SHEET_PIN*>( &aOther );
+
+    double similarity = 1.0;
+
+    if( m_edge != other->m_edge )
+        similarity *= 0.9;
+
+    if( m_number != other->m_number )
+        similarity *= 0.9;
+
+    similarity *= SCH_HIERLABEL::Similarity( aOther );
+
+    return similarity;
+}
+
+
 #if defined(DEBUG)
 
 void SCH_SHEET_PIN::Show( int nestLevel, std::ostream& os ) const
diff --git a/eeschema/sch_sheet_pin.h b/eeschema/sch_sheet_pin.h
index 9b90809c65..7b64223baa 100644
--- a/eeschema/sch_sheet_pin.h
+++ b/eeschema/sch_sheet_pin.h
@@ -200,6 +200,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 private:
     int m_number;       ///< Label number use for saving sheet label to file.
                         ///< Sheet label numbering begins at 2.
diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp
index 70f999ceae..b76c2016ed 100644
--- a/eeschema/sch_symbol.cpp
+++ b/eeschema/sch_symbol.cpp
@@ -2519,6 +2519,61 @@ bool SCH_SYMBOL::IsPower() const
 }
 
 
+bool SCH_SYMBOL::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    auto symbol = static_cast<const SCH_SYMBOL&>( aOther );
+
+    if( GetLibId() != symbol.GetLibId() )
+        return false;
+
+    if( GetPosition() != symbol.GetPosition() )
+        return false;
+
+    if( GetUnit() != symbol.GetUnit() )
+        return false;
+
+    if( GetConvert() != symbol.GetConvert() )
+        return false;
+
+    if( GetTransform() != symbol.GetTransform() )
+        return false;
+
+    if( GetFields() != symbol.GetFields() )
+        return false;
+
+    if( m_pins.size() != symbol.m_pins.size() )
+        return false;
+
+    for( unsigned i = 0; i < m_pins.size(); ++i )
+    {
+        if( !( *m_pins[i] == *symbol.m_pins[i] ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double SCH_SYMBOL::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    auto symbol = static_cast<const SCH_SYMBOL&>( aOther );
+
+    if( GetLibId() != symbol.GetLibId() )
+        return 0.0;
+
+    if( GetPosition() == symbol.GetPosition() )
+        return 1.0;
+
+    return 0.0;
+}
+
+
 static struct SCH_SYMBOL_DESC
 {
     SCH_SYMBOL_DESC()
diff --git a/eeschema/sch_symbol.h b/eeschema/sch_symbol.h
index d079676f21..1872c21eb3 100644
--- a/eeschema/sch_symbol.h
+++ b/eeschema/sch_symbol.h
@@ -758,6 +758,10 @@ public:
 
     bool IsPower() const;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 private:
     BOX2I doGetBoundingBox( bool aIncludePins, bool aIncludeFields ) const;
 
diff --git a/eeschema/sch_text.cpp b/eeschema/sch_text.cpp
index ab3990b4af..173ab1e920 100644
--- a/eeschema/sch_text.cpp
+++ b/eeschema/sch_text.cpp
@@ -438,6 +438,43 @@ void SCH_TEXT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_IT
     aList.emplace_back( _( "Justification" ), msg );
 }
 
+bool SCH_TEXT::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_TEXT* other = static_cast<const SCH_TEXT*>( &aOther );
+
+    if( GetLayer() != other->GetLayer() )
+        return false;
+
+    if( GetExcludedFromSim() != other->GetExcludedFromSim() )
+        return false;
+
+    return EDA_TEXT::operator==( *other );
+}
+
+
+double SCH_TEXT::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    const SCH_TEXT* other = static_cast<const SCH_TEXT*>( &aOther );
+
+    double retval = 1.0;
+
+    if( GetLayer() != other->GetLayer() )
+        retval *= 0.9;
+
+    if( GetExcludedFromSim() != other->GetExcludedFromSim() )
+        retval *= 0.9;
+
+    retval *= EDA_TEXT::Similarity( *other );
+
+    return retval;
+}
+
 
 #if defined(DEBUG)
 
diff --git a/eeschema/sch_text.h b/eeschema/sch_text.h
index f49e2c2381..ffb6b191d8 100644
--- a/eeschema/sch_text.h
+++ b/eeschema/sch_text.h
@@ -138,6 +138,10 @@ public:
 
     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
 
+    virtual double Similarity( const SCH_ITEM& aItem ) const override;
+
+    virtual bool operator==( const SCH_ITEM& aItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/sch_textbox.cpp b/eeschema/sch_textbox.cpp
index 214f68f577..8f2905f9e6 100644
--- a/eeschema/sch_textbox.cpp
+++ b/eeschema/sch_textbox.cpp
@@ -466,6 +466,39 @@ void SCH_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL
 }
 
 
+bool SCH_TEXTBOX::operator==( const SCH_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const SCH_TEXTBOX& other = static_cast<const SCH_TEXTBOX&>( aOther );
+
+    if( m_excludedFromSim != other.m_excludedFromSim )
+        return false;
+
+    return SCH_SHAPE::operator==( aOther ) && EDA_TEXT::operator==( other );
+}
+
+
+double SCH_TEXTBOX::Similarity( const SCH_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    auto other = static_cast<const SCH_TEXTBOX&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_excludedFromSim != other.m_excludedFromSim )
+        similarity *= 0.9;
+
+    similarity *= SCH_SHAPE::Similarity( aOther );
+    similarity *= EDA_TEXT::Similarity( other );
+
+    return similarity;
+}
+
+
 static struct SCH_TEXTBOX_DESC
 {
     SCH_TEXTBOX_DESC()
diff --git a/eeschema/sch_textbox.h b/eeschema/sch_textbox.h
index c116bbc728..4a8cc436cf 100644
--- a/eeschema/sch_textbox.h
+++ b/eeschema/sch_textbox.h
@@ -120,6 +120,10 @@ public:
 
     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
 
+    double Similarity( const SCH_ITEM& aOther ) const override;
+
+    bool operator==( const SCH_ITEM& aOther ) const override;
+
 protected:
     KIFONT::FONT* getDrawFont() const override;
 
diff --git a/eeschema/symbol_diff_frame.cpp b/eeschema/symbol_diff_frame.cpp
new file mode 100644
index 0000000000..53d371e08f
--- /dev/null
+++ b/eeschema/symbol_diff_frame.cpp
@@ -0,0 +1,211 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#include <symbol_diff_frame.h>
+#include <sch_base_frame.h>
+
+#include <wx/event.h>
+
+BEGIN_EVENT_TABLE( SYMBOL_DIFF_FRAME, SCH_BASE_FRAME )
+    // Window events
+    EVT_SIZE( SYMBOL_DIFF_FRAME::OnSize )
+    EVT_ACTIVATE( SYMBOL_DIFF_FRAME::OnActivate )
+
+    // Toolbar events
+    // EVT_TOOL( ID_LIBVIEW_SELECT_PART, SYMBOL_DIFF_FRAME::OnSelectSymbol )
+    // EVT_TOOL( ID_LIBVIEW_NEXT, SYMBOL_DIFF_FRAME::onSelectNextSymbol )
+    // EVT_TOOL( ID_LIBVIEW_PREVIOUS, SYMBOL_DIFF_FRAME::onSelectPreviousSymbol )
+    // EVT_CHOICE( ID_LIBVIEW_SELECT_UNIT_NUMBER, SYMBOL_DIFF_FRAME::onSelectSymbolUnit )
+
+    // listbox events
+    // EVT_TEXT( ID_LIBVIEW_LIB_FILTER, SYMBOL_DIFF_FRAME::OnLibFilter )
+    // EVT_LISTBOX( ID_LIBVIEW_LIB_LIST, SYMBOL_DIFF_FRAME::ClickOnLibList )
+    // EVT_TEXT( ID_LIBVIEW_SYM_FILTER, SYMBOL_DIFF_FRAME::OnSymFilter )
+    // EVT_LISTBOX( ID_LIBVIEW_SYM_LIST, SYMBOL_DIFF_FRAME::ClickOnSymbolList )
+    // EVT_LISTBOX_DCLICK( ID_LIBVIEW_SYM_LIST, SYMBOL_DIFF_FRAME::DClickOnSymbolList )
+
+    // Menu (and/or hotkey) events
+    EVT_MENU( wxID_CLOSE, SYMBOL_DIFF_FRAME::CloseLibraryViewer )
+
+    EVT_UPDATE_UI( ID_LIBVIEW_SELECT_UNIT_NUMBER, SYMBOL_DIFF_FRAME::onUpdateUnitChoice )
+
+END_EVENT_TABLE()
+
+#define LIB_VIEW_STYLE ( KICAD_DEFAULT_DRAWFRAME_STYLE )
+#define LIB_VIEW_STYLE_MODAL ( KICAD_DEFAULT_DRAWFRAME_STYLE | wxFRAME_FLOAT_ON_PARENT )
+
+SYMBOL_DIFF_FRAME::SYMBOL_DIFF_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType,
+                                          const wxString& aLibraryName ) :
+    SCH_BASE_FRAME( aKiway, aParent, aFrameType, _( "Symbol Library Browser" ),
+                    wxDefaultPosition, wxDefaultSize, wxS( "SymbolDiff" ) )
+{
+    SetModal( true );
+
+    m_aboutTitle = _HKI( "KiCad Symbol Difference Viewer" );
+
+    // Give an icon
+    wxIcon  icon;
+    icon.CopyFromBitmap( KiBitmap( BITMAPS::library_browser ) );
+    SetIcon( icon );
+
+
+    SetScreen( new SCH_SCREEN );
+    GetScreen()->m_Center = true;      // Axis origin centered on screen.
+    LoadSettings( config() );
+
+    // Ensure axis are always drawn (initial default display was not drawn)
+    KIGFX::GAL_DISPLAY_OPTIONS& gal_opts = GetGalDisplayOptions();
+    gal_opts.m_axesEnabled = true;
+    gal_opts.m_gridMinSpacing = 10.0;
+    gal_opts.NotifyChanged();
+
+    GetRenderSettings()->LoadColors( GetColorSettings() );
+    GetCanvas()->GetGAL()->SetAxesColor( m_colorSettings->GetColor( LAYER_SCHEMATIC_GRID_AXES ) );
+
+    GetRenderSettings()->SetDefaultPenWidth( DEFAULT_LINE_WIDTH_MILS * schIUScale.IU_PER_MILS );
+
+    setupTools();
+    setupUIConditions();
+
+    ReCreateHToolbar();
+    ReCreateVToolbar();
+    ReCreateMenuBar();
+
+    wxPanel* libPanel = new wxPanel( this );
+    wxSizer* libSizer = new wxBoxSizer( wxVERTICAL );
+
+
+    wxPanel* symbolPanel = new wxPanel( this );
+    wxSizer* symbolSizer = new wxBoxSizer( wxVERTICAL );
+
+    m_symbolFilter = new wxSearchCtrl( symbolPanel, ID_LIBVIEW_SYM_FILTER, wxEmptyString,
+                                       wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
+    m_symbolFilter->SetDescriptiveText( _( "Filter" ) );
+    m_symbolFilter->SetToolTip(
+            _( "Filter on symbol name, keywords, description and pin count.\n"
+               "Search terms are separated by spaces.  All search terms must match.\n"
+               "A term which is a number will also match against the pin count." ) );
+    symbolSizer->Add( m_symbolFilter, 0, wxEXPAND, 5 );
+
+#ifdef __WXGTK__
+    // wxSearchCtrl vertical height is not calculated correctly on some GTK setups
+    // See https://gitlab.com/kicad/code/kicad/-/issues/9019
+    m_libFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) );
+    m_symbolFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) );
+#endif
+
+    m_symbolList = new WX_LISTBOX( symbolPanel, ID_LIBVIEW_SYM_LIST, wxDefaultPosition, wxDefaultSize,
+                                   0, nullptr, wxLB_HSCROLL | wxNO_BORDER );
+    symbolSizer->Add( m_symbolList, 1, wxEXPAND, 5 );
+
+    symbolPanel->SetSizer( symbolSizer );
+    symbolPanel->Fit();
+
+    // Preload libraries
+    loadAllLibraries();
+
+    if( aLibraryName.empty() )
+    {
+        ReCreateLibList();
+    }
+    else
+    {
+        m_currentSymbol.SetLibNickname( aLibraryName );
+        m_currentSymbol.SetLibItemName( "" );
+        m_unit = 1;
+        m_convert = 1;
+    }
+
+    m_selection_changed = false;
+
+    DisplayLibInfos();
+
+    m_auimgr.SetManagedWindow( this );
+
+    CreateInfoBar();
+
+    // Manage main toolbar
+    m_auimgr.AddPane( m_mainToolBar, EDA_PANE().HToolbar().Name( "MainToolbar" ).Top().Layer(6) );
+    m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( "MsgPanel" ) .Bottom().Layer(6) );
+
+    m_auimgr.AddPane( libPanel, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(2)
+                      .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 200, -1 ) );
+    m_auimgr.AddPane( symbolPanel, EDA_PANE().Palette().Name( "Symbols" ).Left().Layer(1)
+                      .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 300, -1 ) );
+
+    m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ).Center() );
+
+    m_auimgr.GetPane( libPanel ).Show( aLibraryName.empty() );
+
+    m_auimgr.Update();
+
+    if( m_libListWidth > 0 )
+        SetAuiPaneSize( m_auimgr, m_auimgr.GetPane( "Libraries" ), m_libListWidth, -1 );
+
+    if( m_symbolListWidth > 0 )
+        SetAuiPaneSize( m_auimgr, m_auimgr.GetPane( "Symbols" ), m_symbolListWidth, -1 );
+
+    FinishAUIInitialization();
+
+    if( !IsModal() )        // For modal mode, calling ShowModal() will show this frame
+    {
+        Raise();
+        Show( true );
+    }
+
+    SyncView();
+    GetCanvas()->SetCanFocus( false );
+
+    setupUnits( config() );
+
+    // Set the working/draw area size to display a symbol to a reasonable value:
+    // A 450mm x 450mm with a origin at the area center looks like a large working area
+    double max_size_x = schIUScale.mmToIU( 450 );
+    double max_size_y = schIUScale.mmToIU( 450 );
+    BOX2D bbox;
+    bbox.SetOrigin( -max_size_x / 2, -max_size_y / 2 );
+    bbox.SetSize( max_size_x, max_size_y );
+    GetCanvas()->GetView()->SetBoundary( bbox );
+    GetToolManager()->RunAction( ACTIONS::zoomFitScreen );
+
+    // If a symbol was previously selected in m_symbolList from a previous run, show it
+    wxString symbName = m_symbolList->GetStringSelection();
+
+    if( !symbName.IsEmpty() )
+    {
+        SetSelectedSymbol( symbName );
+        updatePreviewSymbol();
+    }
+}
+
+
+SYMBOL_DIFF_FRAME::~SYMBOL_DIFF_FRAME()
+{
+    // Shutdown all running tools
+    if( m_toolManager )
+        m_toolManager->ShutdownAllTools();
+
+    if( m_previewItem )
+    {
+        GetCanvas()->GetView()->Remove( m_previewItem.get() );
+        m_previewItem = nullptr;
+    }
+}
\ No newline at end of file
diff --git a/eeschema/symbol_diff_frame.h b/eeschema/symbol_diff_frame.h
new file mode 100644
index 0000000000..50961efd42
--- /dev/null
+++ b/eeschema/symbol_diff_frame.h
@@ -0,0 +1,97 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef __SYMBOL_DIFF_FRAME_H_
+#define __SYMBOL_DIFF_FRAME_H_
+
+#include <sch_base_frame.h>
+
+/**
+ * Symbol library viewer main window.
+ */
+class SYMBOL_DIFF_FRAME : public SCH_BASE_FRAME
+{
+public:
+
+    /**
+     * @param aKiway
+     * @param aParent is the parent frame of the viewer.
+     * @param aFrameType must be either #FRAME_SCH_LIB_VIEWER or #FRAME_SCH_LIB_VIEWER_MODAL.
+     * @param aLibrary is the library to open when starting (default = NULL).
+     */
+    SYMBOL_DIFF_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType,
+                         const wxString& aLibraryName = wxEmptyString );
+
+    ~SYMBOL_DIFF_FRAME();
+
+    /**
+     * Runs the symbol viewer as a modal dialog.
+     *
+     * @param aSymbol an optional FPID string to initialize the viewer with and to
+     *                return a selected footprint through.
+     */
+    bool ShowModal( wxString* aSymbol, wxWindow* aParent ) override;
+
+    /**
+     * Send the selected symbol back to the caller.
+     */
+    void FinishModal();
+
+    void OnSize( wxSizeEvent& event ) override;
+
+    void doCloseWindow() override;
+    void ReCreateHToolbar() override;
+    void ReCreateVToolbar() override;
+    void ReCreateOptToolbar() override {}
+
+    void LoadSettings( APP_SETTINGS_BASE* aCfg ) override;
+    void SaveSettings( APP_SETTINGS_BASE* aCfg ) override;
+
+    WINDOW_SETTINGS* GetWindowSettings( APP_SETTINGS_BASE* aCfg ) override;
+
+    void CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ) override;
+
+    const BOX2I GetDocumentExtents( bool aIncludeAllVisible = true ) const override;
+
+    SELECTION& GetCurrentSelection() override;
+
+    void KiwayMailIn( KIWAY_EXPRESS& mail ) override;
+
+protected:
+    void setupUIConditions() override;
+
+    void doReCreateMenuBar() override;
+
+private:
+    // Set up the tool framework.
+    void setupTools();
+
+    /**
+     * Called when the frame is activated to reload the libraries and symbol lists
+     * that can be changed by the schematic editor or the library editor.
+     */
+    void OnActivate( wxActivateEvent& event );
+
+
+    DECLARE_EVENT_TABLE()
+};
+#endif
\ No newline at end of file
diff --git a/include/advanced_config.h b/include/advanced_config.h
index 6886ad1c09..0967ce3b50 100644
--- a/include/advanced_config.h
+++ b/include/advanced_config.h
@@ -279,6 +279,11 @@ public:
      */
     bool m_EnableGenerators;
 
+    /**
+     * When true, enable git integration
+     */
+    bool m_EnableGit;
+
 ///@}
 
 
diff --git a/include/bitmaps/bitmaps_list.h b/include/bitmaps/bitmaps_list.h
index 55dda436f5..b8f23dd0be 100644
--- a/include/bitmaps/bitmaps_list.h
+++ b/include/bitmaps/bitmaps_list.h
@@ -218,6 +218,13 @@ enum class BITMAPS : unsigned int
     gerber_file,
     gerbview_clear_layers,
     gerbview_show_negative_objects,
+    git_add,
+    git_changed_ahead,
+    git_modified,
+    git_conflict,
+    git_delete,
+    git_good_check,
+    git_out_of_date,
     go_down,
     go_up,
     grid,
diff --git a/include/board_item.h b/include/board_item.h
index 29e6aa1b5b..c3fb34d8d7 100644
--- a/include/board_item.h
+++ b/include/board_item.h
@@ -136,6 +136,15 @@ public:
         return false;
     }
 
+    /**
+     * Return a measure of how likely the other object is to represent the same
+     * object.  The scale runs from 0.0 (definitely different objects) to 1.0 (same)
+     *
+     * This is a pure virtual function.  Derived classes must implement this.
+    */
+    virtual double Similarity( const BOARD_ITEM& aItem ) const = 0;
+    virtual bool operator==( const BOARD_ITEM& aItem ) const = 0;
+
     /**
      * @return true if the object is on any copper layer, false otherwise.
      */
@@ -416,6 +425,16 @@ public:
         return item;
     }
 
+    double Similarity( const BOARD_ITEM& aItem ) const override
+    {
+        return ( this == &aItem ) ? 1.0 : 0.0;
+    }
+
+    bool operator==( const BOARD_ITEM& aItem ) const override
+    {
+        return ( this == &aItem );
+    }
+
 #if defined(DEBUG)
     void Show( int , std::ostream& ) const override {}
 #endif
diff --git a/include/eda_item.h b/include/eda_item.h
index 1d34b851f1..26ba280c83 100644
--- a/include/eda_item.h
+++ b/include/eda_item.h
@@ -503,6 +503,22 @@ private:
  */
 inline EDA_ITEM* new_clone( const EDA_ITEM& aItem ) { return aItem.Clone(); }
 
+/**
+ * Comparison functor for sorting EDA_ITEM pointers by their UUID.
+ */
+struct CompareByUuid
+{
+    bool operator()(const EDA_ITEM* item1, const EDA_ITEM* item2) const
+    {
+        assert( item1 != nullptr && item2 != nullptr );
+
+        if( item1->m_Uuid == item2->m_Uuid )
+            return item1 < item2;
+
+        return item1->m_Uuid < item2->m_Uuid;
+    }
+};
+
 
 /**
  * Define list of drawing items for screens.
@@ -512,4 +528,6 @@ inline EDA_ITEM* new_clone( const EDA_ITEM& aItem ) { return aItem.Clone(); }
  */
 typedef std::vector< EDA_ITEM* > EDA_ITEMS;
 
+typedef std::set< EDA_ITEM*, CompareByUuid > EDA_ITEM_SET;
+
 #endif // EDA_ITEM_H
diff --git a/include/eda_shape.h b/include/eda_shape.h
index 018b22f3b7..4388f7508d 100644
--- a/include/eda_shape.h
+++ b/include/eda_shape.h
@@ -331,6 +331,10 @@ public:
 
     int Compare( const EDA_SHAPE* aOther ) const;
 
+    double Similarity( const EDA_SHAPE& aOther ) const;
+
+    bool operator==( const EDA_SHAPE& aOther ) const;
+
 protected:
     void    setPosition( const VECTOR2I& aPos );
     VECTOR2I getPosition() const;
diff --git a/include/eda_text.h b/include/eda_text.h
index 2e37dc173e..fa83069dd0 100644
--- a/include/eda_text.h
+++ b/include/eda_text.h
@@ -303,6 +303,16 @@ public:
      */
     void GetLinePositions( std::vector<VECTOR2I>& aPositions, int aLineCount ) const;
 
+    /**
+     * Return the levenstein distance between two texts.
+     *
+     * Return a value of 0.0 - 1.0 where 1.0 is a perfect match.
+    */
+    double Levenshtein( const EDA_TEXT& aOther ) const;
+
+
+    double Similarity( const EDA_TEXT& aOther ) const;
+
     /**
      * Output the object to \a aFormatter in s-expression form.
      *
diff --git a/include/frame_type.h b/include/frame_type.h
index cb20489465..9970d58da6 100644
--- a/include/frame_type.h
+++ b/include/frame_type.h
@@ -36,6 +36,8 @@ enum FRAME_T
     FRAME_SCH_VIEWER,
     FRAME_SYMBOL_CHOOSER,
     FRAME_SIMULATOR,
+    FRAME_SCH_DIFF,
+    FRAME_SYM_DIFF,
 
     FRAME_PCB_EDITOR,
     FRAME_FOOTPRINT_EDITOR,
@@ -44,6 +46,8 @@ enum FRAME_T
     FRAME_FOOTPRINT_WIZARD,
     FRAME_PCB_DISPLAY3D,
     FRAME_FOOTPRINT_PREVIEW,
+    FRAME_PCB_DIFF,
+    FRAME_FOOTPRINT_DIFF,
 
     FRAME_CVPCB,
     FRAME_CVPCB_DISPLAY,
diff --git a/include/gestfich.h b/include/gestfich.h
index ce488518ed..52d558d485 100644
--- a/include/gestfich.h
+++ b/include/gestfich.h
@@ -92,4 +92,11 @@ wxString FindKicadFile( const wxString& shortname );
  */
 extern wxString QuoteFullPath( wxFileName& fn, wxPathFormat format = wxPATH_NATIVE );
 
+
+/**
+ * Removes the directory \a aDirName and all its contents including
+ * subdirectories and their files
+ */
+bool RmDirRecursive( const wxString& aDirName, wxString* aErrors = nullptr );
+
 #endif /* GESTFICH_H */
diff --git a/include/pcb_group.h b/include/pcb_group.h
index dbe08c11e2..d277edbfdc 100644
--- a/include/pcb_group.h
+++ b/include/pcb_group.h
@@ -102,6 +102,10 @@ public:
 
     static bool WithinScope( BOARD_ITEM* aItem, PCB_GROUP* aScope, bool isFootprintEditor );
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 #if defined( DEBUG )
     void Show( int nestLevel, std::ostream& os ) const override
     {
diff --git a/include/project/project_local_settings.h b/include/project/project_local_settings.h
index 77ba3c37c0..73505429e4 100644
--- a/include/project/project_local_settings.h
+++ b/include/project/project_local_settings.h
@@ -135,6 +135,12 @@ public:
     /// State of the selection filter widget
     SELECTION_FILTER_OPTIONS m_SelectionFilter;
 
+    // Upstream git repo info
+    wxString m_GitRepoUsername;
+    wxString m_GitRepoPassword;
+    wxString m_GitRepoType;
+    wxString m_GitSSHKey;
+
 private:
     /// A link to the owning project
     PROJECT* m_project;
diff --git a/include/scoped_set_reset.h b/include/scoped_set_reset.h
index ff84795e8b..be02be67be 100644
--- a/include/scoped_set_reset.h
+++ b/include/scoped_set_reset.h
@@ -24,6 +24,8 @@
 #ifndef __SCOPED_SET_RESET_H
 #define __SCOPED_SET_RESET_H
 
+#include <functional>
+
 /**
  * RAII class that sets an value at construction and resets it to the original value
  * at destruction.
@@ -65,4 +67,30 @@ private:
     VAL_TYPE& m_target;
 };
 
+
+/**
+ * RAII class that executes a function at construction and another at destruction.
+ *
+ * Useful to ensure cleanup code is executed even if an exception is thrown.
+*/
+template <typename Func>
+class SCOPED_EXECUTION
+{
+public:
+    SCOPED_EXECUTION(Func initFunc, Func destroyFunc) :
+            m_initFunc(initFunc), m_destroyFunc(destroyFunc)
+    {
+        m_initFunc();
+    }
+
+    ~SCOPED_EXECUTION()
+    {
+        m_destroyFunc();
+    }
+
+private:
+    Func m_initFunc;
+    Func m_destroyFunc;
+};
+
 #endif // __SCOPED_SET_RESET_H
diff --git a/include/settings/common_settings.h b/include/settings/common_settings.h
index e2194d7319..90c9c97821 100644
--- a/include/settings/common_settings.h
+++ b/include/settings/common_settings.h
@@ -145,6 +145,25 @@ public:
         int sash_pos;
     };
 
+    struct GIT_REPOSITORY
+    {
+        wxString name;
+        wxString path;
+        wxString authType;
+        wxString username;
+        wxString ssh_path;
+        bool     active;
+        bool     checkValid;
+    };
+
+    struct GIT
+    {
+        std::vector<GIT_REPOSITORY> repositories;
+        bool                        useDefaultAuthor;
+        wxString                    authorName;
+        wxString                    authorEmail;
+    };
+
     COMMON_SETTINGS();
 
     virtual ~COMMON_SETTINGS() {}
@@ -193,6 +212,8 @@ public:
     NETCLASS_PANEL m_NetclassPanel;
 
     PACKAGE_MANAGER m_PackageManager;
+
+    GIT m_Git;
 };
 
 #endif
diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp
index fde8d69688..05e0353848 100644
--- a/kicad/kicad.cpp
+++ b/kicad/kicad.cpp
@@ -47,6 +47,7 @@
 #include <trace_helpers.h>
 #include <wildcards_and_files_ext.h>
 
+#include <git2.h>
 #include <stdexcept>
 
 #include "pgm_kicad.h"
@@ -105,6 +106,9 @@ bool PGM_KICAD::OnPgmInit()
     }
 #endif
 
+    // Initialize the git library before trying to initialize individual programs
+    git_libgit2_init();
+
     static const wxCmdLineEntryDesc desc[] = {
         { wxCMD_LINE_OPTION, "f", "frame", "Frame to load", wxCMD_LINE_VAL_STRING, 0 },
         { wxCMD_LINE_PARAM, nullptr, nullptr, "File to load", wxCMD_LINE_VAL_STRING,
@@ -378,6 +382,7 @@ void PGM_KICAD::OnPgmExit()
     // especially wxSingleInstanceCheckerImpl earlier than wxApp and earlier
     // than static destruction would.
     Destroy();
+    git_libgit2_shutdown();
 }
 
 
diff --git a/kicad/kicad_id.h b/kicad/kicad_id.h
index 326a723aa2..60e6cd8228 100644
--- a/kicad/kicad_id.h
+++ b/kicad/kicad_id.h
@@ -73,6 +73,27 @@ enum id_kicad_frm {
     ID_IMPORT_EASYEDA_PROJECT,
     ID_IMPORT_EASYEDAPRO_PROJECT,
 
+    ID_GIT_INITIALIZE_PROJECT,  // Initialize a new git repository in an existing project
+    ID_GIT_CLONE_PROJECT,       // Clone a project from a remote repository
+    ID_GIT_COMMIT_PROJECT,      // Commit all files in the project
+    ID_GIT_COMMIT_FILE,         // Commit a single file
+    ID_GIT_SYNC_PROJECT,        // Sync the project with the remote repository (pull and push -- same as Update)
+    ID_GIT_FETCH,               // Fetch the remote repository (without merging -- this is the same as Refresh)
+    ID_GIT_PUSH,                // Push the local repository to the remote repository
+    ID_GIT_PULL,                // Pull the remote repository to the local repository
+    ID_GIT_RESOLVE_CONFLICT,    // Present the user with a resolve conflicts dialog (ours/theirs/merge)
+    ID_GIT_REVERT_LOCAL,        // Revert the local repository to the last commit
+    ID_GIT_COMPARE,             // Compare the current project to a different branch or commit in the git repository
+    ID_GIT_REMOVE_VCS,          // Remove the git repository data from the project directory (rm .git)
+    ID_GIT_ADD_TO_INDEX,        // Add a file to the git index
+    ID_GIT_REMOVE_FROM_INDEX,   // Remove a file from the git index
+    ID_GIT_SWITCH_BRANCH,       // Switch the local repository to a different branch
+    ID_GIT_SWITCH_QUICK1,       // Switch the local repository to the first quick branch
+    ID_GIT_SWITCH_QUICK2,       // Switch the local repository to the second quick branch
+    ID_GIT_SWITCH_QUICK3,       // Switch the local repository to the third quick branch
+    ID_GIT_SWITCH_QUICK4,       // Switch the local repository to the fourth quick branch
+    ID_GIT_SWITCH_QUICK5,       // Switch the local repository to the fifth quick branch
+
     // Please, verify: the number of items in this list should be
     // less than ROOM_FOR_KICADMANAGER (see id.h)
     ID_KICADMANAGER_END_LIST
diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp
index 0515ac5bc7..6114abfd33 100644
--- a/kicad/menubar.cpp
+++ b/kicad/menubar.cpp
@@ -74,6 +74,7 @@ void KICAD_MANAGER_FRAME::doReCreateMenuBar()
 
     fileMenu->Add( KICAD_MANAGER_ACTIONS::newProject );
     fileMenu->Add( KICAD_MANAGER_ACTIONS::newFromTemplate );
+    fileMenu->Add( KICAD_MANAGER_ACTIONS::newFromRepository );
 
     if( wxDir::Exists( PATHS::GetStockDemosPath() ) )
     {
diff --git a/kicad/project_tree.cpp b/kicad/project_tree.cpp
index 148c306b56..3d6f215048 100644
--- a/kicad/project_tree.cpp
+++ b/kicad/project_tree.cpp
@@ -24,6 +24,7 @@
 
 
 #include <bitmaps.h>
+#include <git/kicad_git_common.h>
 #include <wx/settings.h>
 
 #include "project_tree_item.h"
@@ -48,6 +49,7 @@ PROJECT_TREE::PROJECT_TREE( PROJECT_TREE_PANE* parent ) :
         m_imageList( nullptr )
 {
     m_projectTreePane = parent;
+    m_gitCommon = new KIGIT_COMMON( nullptr );
 
     // Make sure the GUI font scales properly on GTK
     SetFont( KIUI::GetControlFont( this ) );
@@ -65,6 +67,7 @@ PROJECT_TREE::~PROJECT_TREE()
 void PROJECT_TREE::LoadIcons()
 {
     delete m_imageList;
+    delete m_statusImageList;
 
     // icons size is not know (depending on they are built)
     // so get it:
@@ -107,6 +110,28 @@ void PROJECT_TREE::LoadIcons()
     m_imageList->Add( KiBitmap( BITMAPS::zip ) );                    // ZIP_ARCHIVE
 
     SetImageList( m_imageList );
+
+    // Make an image list containing small icons
+    dummy = KiBitmap( BITMAPS::git_add );
+    iconsize.x = dummy.GetWidth();
+    iconsize.y = dummy.GetHeight();
+
+    wxBitmap blank_bitmap( iconsize.x, iconsize.y );
+
+    m_statusImageList = new wxImageList( iconsize.x, iconsize.y, true,
+                                         static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_LAST ) );
+
+    m_statusImageList->Add( blank_bitmap );                          // GIT_STATUS_UNTRACKED
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_good_check ) );   // GIT_STATUS_CURRENT
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_modified ) );     // GIT_STATUS_MODIFIED
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_add ) );          // GIT_STATUS_ADDED
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_delete ) );       // GIT_STATUS_DELETED
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_out_of_date ) );  // GIT_STATUS_BEHIND
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_changed_ahead ) );// GIT_STATUS_AHEAD
+    m_statusImageList->Add( KiBitmap( BITMAPS::git_conflict ) );     // GIT_STATUS_CONFLICTED
+
+    SetStateImageList( m_statusImageList );
+
 }
 
 
diff --git a/kicad/project_tree.h b/kicad/project_tree.h
index 67864a407f..80c5a98303 100644
--- a/kicad/project_tree.h
+++ b/kicad/project_tree.h
@@ -25,12 +25,14 @@
 #ifndef PROJECT_TREE_H
 #define PROJECT_TREE_H
 
+#include <git/kicad_git_common.h>
 
 #include <wx/treectrl.h>
 
 #include "tree_file_type.h"
 
 class PROJECT_TREE_PANE;
+struct git_repository;
 
 /** PROJECT_TREE
  * This is the class to show (as a tree) the files in the project directory
@@ -42,6 +44,8 @@ class PROJECT_TREE : public wxTreeCtrl
 private:
     PROJECT_TREE_PANE* m_projectTreePane;
     wxImageList*       m_imageList;
+    wxImageList*       m_statusImageList;
+    KIGIT_COMMON*      m_gitCommon;
 
 public:
     PROJECT_TREE_PANE* GetProjectTreePane() const { return m_projectTreePane; }
@@ -51,6 +55,11 @@ public:
 
     void LoadIcons();
 
+    void SetGitRepo( git_repository* aRepo )    { m_gitCommon->SetRepo( aRepo ); }
+    git_repository* GetGitRepo() const          { return m_gitCommon->GetRepo(); }
+
+    KIGIT_COMMON* GitCommon() const             { return m_gitCommon; }
+
 private:
     /* overridden sort function */
     int OnCompareItems( const wxTreeItemId& item1, const wxTreeItemId& item2 ) override;
diff --git a/kicad/project_tree_pane.cpp b/kicad/project_tree_pane.cpp
index cbe648aad1..3cebfb60b1 100644
--- a/kicad/project_tree_pane.cpp
+++ b/kicad/project_tree_pane.cpp
@@ -24,6 +24,7 @@
  */
 
 #include <stack>
+#include <git2.h>
 
 #include <wx/regex.h>
 #include <wx/stdpaths.h>
@@ -31,8 +32,12 @@
 #include <wx/msgdlg.h>
 #include <wx/textdlg.h>
 
+#include <advanced_config.h>
 #include <bitmaps.h>
 #include <bitmap_store.h>
+#include <confirm.h>
+#include <dialogs/git/dialog_git_commit.h>
+#include <dialogs/git/dialog_git_switch.h>
 #include <gestfich.h>
 #include <macros.h>
 #include <trace_helpers.h>
@@ -40,11 +45,29 @@
 #include <kiplatform/environment.h>
 #include <core/kicad_algo.h>
 #include <paths.h>
+#include <project/project_local_settings.h>
+#include <scoped_set_reset.h>
 #include <string_utils.h>
 #include <launch_ext.h>
 #include <wx/dcclient.h>
 #include <wx/settings.h>
 
+#include <git/git_commit_handler.h>
+#include <git/git_pull_handler.h>
+#include <git/git_push_handler.h>
+#include <git/git_resolve_conflict_handler.h>
+#include <git/git_revert_handler.h>
+#include <git/git_switch_branch_handler.h>
+#include <git/git_compare_handler.h>
+#include <git/git_remove_vcs_handler.h>
+#include <git/git_add_to_index_handler.h>
+#include <git/git_remove_from_index_handler.h>
+#include <git/git_sync_handler.h>
+#include <git/git_clone_handler.h>
+
+
+#include <dialogs/git/dialog_git_repository.h>
+
 #include "project_tree_item.h"
 #include "project_tree.h"
 #include "pgm_kicad.h"
@@ -126,6 +149,23 @@ BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
     EVT_MENU( ID_PROJECT_OPEN_DIR, PROJECT_TREE_PANE::onOpenDirectory )
     EVT_MENU( ID_PROJECT_DELETE, PROJECT_TREE_PANE::onDeleteFile )
     EVT_MENU( ID_PROJECT_RENAME, PROJECT_TREE_PANE::onRenameFile )
+
+    EVT_MENU( ID_GIT_INITIALIZE_PROJECT, PROJECT_TREE_PANE::onGitInitializeProject )
+    EVT_MENU( ID_GIT_COMMIT_PROJECT, PROJECT_TREE_PANE::onGitCommit )
+    EVT_MENU( ID_GIT_COMMIT_FILE, PROJECT_TREE_PANE::onGitCommit )
+    EVT_MENU( ID_GIT_SYNC_PROJECT, PROJECT_TREE_PANE::onGitSyncProject )
+    EVT_MENU( ID_GIT_FETCH, PROJECT_TREE_PANE::onGitFetch )
+    EVT_MENU( ID_GIT_PUSH, PROJECT_TREE_PANE::onGitPushProject )
+    EVT_MENU( ID_GIT_PULL, PROJECT_TREE_PANE::onGitPullProject )
+    EVT_MENU( ID_GIT_RESOLVE_CONFLICT, PROJECT_TREE_PANE::onGitResolveConflict )
+    EVT_MENU( ID_GIT_REVERT_LOCAL, PROJECT_TREE_PANE::onGitRevertLocal )
+    EVT_MENU( ID_GIT_SWITCH_BRANCH, PROJECT_TREE_PANE::onGitSwitchBranch )
+    EVT_MENU( ID_GIT_COMPARE, PROJECT_TREE_PANE::onGitCompare )
+    EVT_MENU( ID_GIT_REMOVE_VCS, PROJECT_TREE_PANE::onGitRemoveVCS )
+    EVT_MENU( ID_GIT_ADD_TO_INDEX, PROJECT_TREE_PANE::onGitAddToIndex )
+    EVT_MENU( ID_GIT_REMOVE_FROM_INDEX, PROJECT_TREE_PANE::onGitRemoveFromIndex )
+
+
     EVT_IDLE( PROJECT_TREE_PANE::onIdle )
     EVT_PAINT( PROJECT_TREE_PANE::onPaint )
 END_EVENT_TABLE()
@@ -140,6 +180,7 @@ PROJECT_TREE_PANE::PROJECT_TREE_PANE( KICAD_MANAGER_FRAME* parent ) :
     m_isRenaming = false;
     m_selectedItem = nullptr;
     m_watcherNeedReset = false;
+    m_lastGitStatusUpdate = wxDateTime::Now();
 
     m_watcher = nullptr;
     Connect( wxEVT_FSWATCHER,
@@ -158,6 +199,7 @@ PROJECT_TREE_PANE::PROJECT_TREE_PANE( KICAD_MANAGER_FRAME* parent ) :
     m_filters.emplace_back( wxT( "^no KiCad files found" ) );
 
     ReCreateTreePrj();
+
 }
 
 
@@ -304,6 +346,30 @@ std::vector<wxString> getProjects( const wxDir& dir )
     return projects;
 }
 
+static git_repository* get_git_repository_for_file( const char* filename )
+{
+    git_repository* repo = nullptr;
+    git_buf         repo_path = GIT_BUF_INIT;
+
+    // Find the repository path for the given file
+    if( git_repository_discover( &repo_path, filename, 0, NULL ) )
+    {
+        printf( "Error %s\n", git_error_last()->message );
+        return nullptr;
+    }
+
+    if( git_repository_open( &repo, repo_path.ptr ) )
+    {
+        git_buf_free( &repo_path );
+        return nullptr;
+    }
+
+    // Free the git_buf memory
+    git_buf_free( &repo_path );
+
+    return repo;
+}
+
 
 wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
                                                       const wxTreeItemId& aParent,
@@ -468,7 +534,9 @@ wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
     // Mark root files (files which have the same aName as the project)
     wxString fileName = currfile.GetName().Lower();
     wxString projName = project.GetName().Lower();
-    data->SetRootFile( fileName == projName || fileName.StartsWith( projName + "-" ) );
+
+    if( fileName == projName || fileName.StartsWith( projName + "-" ) )
+        data->SetRootFile( true );
 
 #ifndef __WINDOWS__
     bool subdir_populated = false;
@@ -527,6 +595,12 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
     if( !pro_dir )  // This is empty from PROJECT_TREE_PANE constructor
         return;
 
+    if( m_TreeProject->GetGitRepo() )
+    {
+        git_repository_free( m_TreeProject->GetGitRepo() );
+        m_TreeProject->SetGitRepo( nullptr );
+    }
+
     wxFileName fn = pro_dir;
     bool prjReset = false;
 
@@ -541,6 +615,21 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
 
     bool prjOpened = fn.FileExists();
 
+    // Bind the git repository to the project tree (if it exists)
+    m_TreeProject->SetGitRepo( get_git_repository_for_file( fn.GetPath().c_str() ) );
+    m_TreeProject->GitCommon()->SetPassword( Prj().GetLocalSettings().m_GitRepoPassword );
+    m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
+    m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
+
+    wxString conn_type = Prj().GetLocalSettings().m_GitRepoType;
+
+    if( conn_type == "https" )
+        m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS );
+    else if( conn_type == "ssh" )
+        m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH );
+    else
+        m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL );
+
     // We may have opened a legacy project, in which case GetProjectFileName will return the
     // name of the migrated (new format) file, which may not have been saved to disk yet.
     if( !prjOpened && !prjReset )
@@ -558,8 +647,10 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
     m_TreeProject->SetItemBold( m_root, true );
 
     // The main project file is now a JSON file
-    m_TreeProject->SetItemData( m_root, new PROJECT_TREE_ITEM( TREE_FILE_TYPE::JSON_PROJECT,
-                                                               fn.GetFullPath(), m_TreeProject ) );
+    PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( TREE_FILE_TYPE::JSON_PROJECT,
+                                                  fn.GetFullPath(), m_TreeProject );
+
+    m_TreeProject->SetItemData( m_root, data );
 
     // Now adding all current files if available
     if( prjOpened )
@@ -596,6 +687,31 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
 
     // Sort filenames by alphabetic order
     m_TreeProject->SortChildren( m_root );
+    updateGitStatusIcons();
+}
+
+
+bool PROJECT_TREE_PANE::hasChangedFiles()
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return false;
+
+    git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+    opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+    opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
+                 | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
+
+    git_status_list* status_list = nullptr;
+    int              error = git_status_list_new( &status_list, repo, &opts );
+
+    if( error != GIT_OK )
+        return false;
+
+    bool has_changed_files = git_status_list_entrycount( status_list ) > 0;
+    git_status_list_free( status_list );
+    return has_changed_files;
 }
 
 
@@ -615,6 +731,22 @@ void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
     bool can_rename = true;
     bool can_delete = true;
 
+    bool vcs_has_repo    = m_TreeProject->GetGitRepo() != nullptr;
+    bool vcs_can_commit  = hasChangedFiles();
+    bool vcs_can_init    = !vcs_has_repo;
+    bool vcs_can_remove  = vcs_has_repo;
+    bool vcs_can_fetch   = vcs_has_repo && m_TreeProject->GitCommon()->HasPushAndPullRemote();
+    bool vcs_can_push    = vcs_can_fetch && m_TreeProject->GitCommon()->HasLocalCommits();
+    bool vcs_can_pull    = vcs_can_fetch;
+    bool vcs_can_switch  = vcs_has_repo;
+    bool vcs_menu        = ADVANCED_CFG::GetCfg().m_EnableGit;
+
+    // Check if the libgit2 library has been successfully initialized
+    int major, minor, rev;
+    bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK );
+
+    vcs_menu &= libgit_init;
+
     if( selection.size() == 0 )
         return;
 
@@ -642,8 +774,8 @@ void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
 
         switch( item->GetType() )
         {
-        case TREE_FILE_TYPE::LEGACY_PROJECT:
         case TREE_FILE_TYPE::JSON_PROJECT:
+        case TREE_FILE_TYPE::LEGACY_PROJECT:
             can_rename = false;
 
             if( item->GetId() == m_TreeProject->GetRootItem() )
@@ -662,6 +794,10 @@ void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
             can_edit = false;
             break;
 
+        case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
+        case TREE_FILE_TYPE::SEXPR_PCB:
+            KI_FALLTHROUGH;
+
         default:
             can_switch_to_project = false;
             can_create_new_directory = false;
@@ -769,6 +905,68 @@ void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
 #endif
     }
 
+    if( vcs_menu )
+    {
+        wxMenu*     vcs_submenu = new wxMenu();
+        wxMenu*     branch_submenu = new wxMenu();
+        wxMenuItem* vcs_menuitem = nullptr;
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
+                                            _( "Add Project to Version Control..." ),
+                                            _( "Initialize a new repository" ) );
+        vcs_menuitem->Enable( vcs_can_init );
+
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
+                                            _( "Commit changes to the local repository" ) );
+        vcs_menuitem->Enable( vcs_can_commit );
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
+                                            _( "Push committed local changes to remote repository" ) );
+        vcs_menuitem->Enable( vcs_can_push );
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
+                                            _( "Pull changes from remote repository into local" ) );
+        vcs_menuitem->Enable( vcs_can_pull );
+
+        vcs_submenu->AppendSeparator();
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
+                                            _( "Commit changes to the local repository" ) );
+        vcs_menuitem->Enable( vcs_can_commit );
+
+        vcs_submenu->AppendSeparator();
+
+        // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
+        //                              _( "Show changes between the repository and working tree" ) );
+        // vcs_menuitem->Enable( vcs_can_diff );
+
+        std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
+
+        // Skip the first one (that is the current branch)
+        for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
+        {
+            wxString msg = _( "Switch to branch " ) + branchNames[ii];
+            vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
+            vcs_menuitem->Enable( vcs_can_switch );
+        }
+
+        vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
+                                     _( "Switch to a different branch" ) );
+        vcs_menuitem->Enable( vcs_can_switch );
+
+        vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
+
+        vcs_submenu->AppendSeparator();
+
+        vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Remove Version Control" ),
+                             _( "Delete all version control files from the project directory." ) );
+        vcs_menuitem->Enable( vcs_can_remove );
+
+        popup_menu.AppendSeparator();
+        popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
+    }
+
     if( popup_menu.GetMenuItemCount() > 0 )
         PopupMenu( &popup_menu );
 }
@@ -872,6 +1070,9 @@ void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
 
         item->Activate( this );
     }
+
+    // Inside this routine, we rate limit to once per 2 seconds
+    updateGitStatusIcons();
 }
 
 
@@ -1042,9 +1243,12 @@ void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
     case wxFSW_EVENT_DELETE:
     case wxFSW_EVENT_CREATE:
     case wxFSW_EVENT_RENAME:
+        CallAfter( &PROJECT_TREE_PANE::updateGitStatusIcons );
         break;
 
     case wxFSW_EVENT_MODIFY:
+        CallAfter( &PROJECT_TREE_PANE::updateGitStatusIcons );
+        KI_FALLTHROUGH;
     case wxFSW_EVENT_ACCESS:
     default:
         return;
@@ -1288,3 +1492,769 @@ void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
 {
     m_leftWin->FileWatcherReset();
 }
+
+
+void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
+{
+    PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
+
+    wxString dir = tree_data->GetDir();
+
+    if( dir.empty() )
+    {
+        wxLogError( "Failed to initialize git project: project directory is empty." );
+        return;
+    }
+
+    // Check if the directory is already a git repository
+    git_repository* repo = nullptr;
+    int error = git_repository_open(&repo, dir.mb_str());
+
+    if( error == 0 )
+    {
+        // Directory is already a git repository
+
+        DisplayInfoMessage( this, _( "The selected directory is already a git project." ) );
+        git_repository_free( repo );
+        return;
+    }
+    else
+    {
+        // Directory is not a git repository
+        error = git_repository_init( &repo, dir.mb_str(), 0 );
+
+        if( error != 0 )
+        {
+            git_repository_free( repo );
+            DisplayErrorMessage( this, _( "Failed to initialize git project." ),
+                                 git_error_last()->message );
+            return;
+        }
+        else
+        {
+            m_TreeProject->SetGitRepo( repo );
+        }
+    }
+
+    DIALOG_GIT_REPOSITORY dlg( this, repo );
+
+    dlg.SetTitle( _( "Set default remote" ) );
+
+    if( dlg.ShowModal() != wxID_OK )
+        return;
+
+    //Set up the git remote
+
+    m_TreeProject->GitCommon()->SetConnType( dlg.GetRepoType() );
+    m_TreeProject->GitCommon()->SetPassword( dlg.GetPassword() );
+    m_TreeProject->GitCommon()->SetUsername( dlg.GetUsername() );
+    m_TreeProject->GitCommon()->SetSSHKey( dlg.GetRepoSSHPath() );
+
+    git_remote* remote = nullptr;
+    wxString fullURL;
+
+    if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
+    {
+        fullURL = dlg.GetUsername() + "@" + dlg.GetRepoURL();
+    }
+    else if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
+    {
+        fullURL = dlg.GetRepoURL().StartsWith( "https" ) ? "https://" : "http://";
+
+        if( !dlg.GetUsername().empty() )
+        {
+            fullURL.append( dlg.GetUsername() );
+
+            if( !dlg.GetPassword().empty() )
+            {
+                fullURL.append( wxS( ":" ) );
+                fullURL.append( dlg.GetPassword() );
+            }
+
+            fullURL.append( wxS( "@" ) );
+        }
+
+        fullURL.append( dlg.GetBareRepoURL() );
+    }
+    else
+    {
+        fullURL = dlg.GetRepoURL();
+    }
+
+
+    error = git_remote_create_with_fetchspec( &remote, repo, "origin",
+                                              fullURL.ToStdString().c_str(),
+                                              "+refs/heads/*:refs/remotes/origin/*" );
+
+    if( error != GIT_OK )
+    {
+        DisplayErrorMessage( this, _( "Failed to set default remote." ),
+                             git_error_last()->message );
+        return;
+    }
+
+    GIT_PULL_HANDLER handler( repo );
+
+    handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
+    handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
+    handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
+    handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
+
+    handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
+
+    handler.PerformFetch();
+
+    Prj().GetLocalSettings().m_GitRepoPassword = dlg.GetPassword();
+    Prj().GetLocalSettings().m_GitRepoUsername = dlg.GetUsername();
+    Prj().GetLocalSettings().m_GitSSHKey = dlg.GetRepoSSHPath();
+
+    if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
+        Prj().GetLocalSettings().m_GitRepoType = "ssh";
+    else if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
+        Prj().GetLocalSettings().m_GitRepoType = "https";
+    else
+        Prj().GetLocalSettings().m_GitRepoType = "local";
+}
+
+
+void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
+{
+
+}
+
+
+void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_PULL_HANDLER handler( repo );
+
+    handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
+    handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
+    handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
+    handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
+
+    handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
+
+    handler.PerformPull();
+}
+
+
+void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_PUSH_HANDLER handler( repo );
+
+    handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
+    handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
+    handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
+    handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
+
+    handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
+
+    if( handler.PerformPush() != PushResult::Success )
+    {
+        wxString errorMessage = handler.GetErrorString();
+
+        DisplayErrorMessage( this, _( "Failed to push project" ), errorMessage );
+    }
+}
+
+
+static int git_create_branch( git_repository* aRepo, wxString& aBranchName )
+{
+    git_oid        head_oid;
+
+    if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ) != 0 )
+    {
+        wxLogError( "Failed to lookup HEAD reference" );
+        return error;
+    }
+
+    // Lookup the current commit object
+    git_commit* commit = nullptr;
+    if( int error = git_commit_lookup( &commit, aRepo, &head_oid ) != GIT_OK )
+    {
+        wxLogError( "Failed to lookup commit" );
+        return error;
+    }
+
+    git_reference* branchRef = nullptr;
+
+    if( git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ) != 0 )
+    {
+        wxLogError( "Failed to create branch" );
+        git_commit_free( commit );
+        return -1;
+    }
+
+    git_commit_free( commit );
+    git_reference_free( branchRef );
+
+    return 0;
+}
+
+
+void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    DIALOG_GIT_SWITCH dlg( this, repo );
+
+    int retval = dlg.ShowModal();
+    wxString branchName = dlg.GetBranchName();
+
+    if( retval == wxID_ADD )
+        git_create_branch( repo, branchName);
+    else if( retval != wxID_OK )
+        return;
+
+    // Retrieve the reference to the existing branch using libgit2
+    git_reference* branchRef = nullptr;
+
+    if( git_reference_lookup( &branchRef, repo, branchName.mb_str() ) != GIT_OK &&
+        git_reference_dwim( &branchRef, repo, branchName.mb_str() ) != GIT_OK )
+    {
+        wxString errorMessage = wxString::Format( _( "Failed to lookup branch '%s': %s" ), branchName, giterr_last()->message );
+        DisplayError( this, errorMessage );
+        return;
+    }
+
+    const char* branchRefName = git_reference_name( branchRef );
+
+    git_object* branchObj = nullptr;
+
+    if( git_revparse_single( &branchObj, repo, branchName.mb_str() ) != 0 )
+    {
+        wxString errorMessage =
+                wxString::Format( _( "Failed to find branch head for '%s'" ), branchName );
+        DisplayError( this, errorMessage );
+        git_reference_free( branchRef );
+        return;
+    }
+
+
+    // Switch to the branch
+    if( git_checkout_tree( repo, branchObj, nullptr ) != 0 )
+    {
+        wxString errorMessage =
+                wxString::Format( _( "Failed to switch to branch '%s'" ), branchName );
+        DisplayError( this, errorMessage );
+        git_reference_free( branchRef );
+        git_object_free( branchObj );
+        return;
+    }
+
+    // Update the HEAD reference
+    if( git_repository_set_head( repo, branchRefName ) != 0 )
+    {
+        wxString errorMessage = wxString::Format(
+                _( "Failed to update HEAD reference for branch '%s'" ), branchName );
+        DisplayError( this, errorMessage );
+        git_reference_free( branchRef );
+        git_object_free( branchObj );
+        return;
+    }
+
+    // Free resources
+    git_reference_free( branchRef );
+    git_object_free( branchObj );
+}
+
+
+void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo
+        || !IsOK( this, _( "Are you sure you want to remove git tracking from this project?" ) ) )
+    {
+        return;
+    }
+
+    // Remove the VCS (git) from the project directory
+    git_repository_free( repo );
+    m_TreeProject->SetGitRepo( nullptr );
+
+    // Remove the .git directory
+    wxFileName fn( m_Parent->GetProjectFileName() );
+    fn.AppendDir( ".git" );
+
+    wxString errors;
+
+    if( !RmDirRecursive( fn.GetPath(), &errors ) )
+    {
+        DisplayErrorMessage( this, _( "Failed to remove git directory" ), errors );
+    }
+
+    // Clear all item states
+
+    for( wxTreeItemId cookie = m_TreeProject->GetRootItem();
+         cookie != nullptr;
+         cookie = m_TreeProject->GetNext( cookie ) )
+    {
+        m_TreeProject->SetItemState( cookie, wxTREE_ITEMSTATE_NONE );
+    }
+}
+
+
+void PROJECT_TREE_PANE::updateGitStatusIcons()
+{
+    if( ADVANCED_CFG::GetCfg().m_EnableGit == false )
+        return;
+
+    wxTimeSpan timeSinceLastUpdate = wxDateTime::Now() - m_lastGitStatusUpdate;
+
+    if( timeSinceLastUpdate.Abs() < wxTimeSpan::Seconds( 2 ) )
+        return;
+
+    m_lastGitStatusUpdate = wxDateTime::Now();
+
+    wxTreeItemId      kid = m_TreeProject->GetRootItem();
+
+    if( !kid.IsOk() )
+        return;
+
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    // Get Current Branch
+
+    git_reference* currentBranchReference = nullptr;
+    git_repository_head( &currentBranchReference, repo );
+
+    // Get the current branch name
+    if( currentBranchReference )
+    {
+        PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
+        wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
+        wxString branchName = git_reference_shorthand( currentBranchReference );
+
+        m_TreeProject->SetItemText( kid, filename + " [" + branchName + "]" );
+        git_reference_free( currentBranchReference );
+    }
+    else
+    {
+        wxLogError( "Failed to lookup current branch: %s", giterr_last()->message );
+    }
+
+    // Collect a map to easily set the state of each item
+    std::map<wxString, wxTreeItemId> branchMap;
+    {
+        while( kid.IsOk() )
+        {
+            PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
+
+            branchMap[nextItem->GetFileName()] = kid;
+            kid = m_TreeProject->GetNext( kid );
+        }
+    }
+
+    git_status_options status_options = GIT_STATUS_OPTIONS_INIT;
+    status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+    status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+
+    git_index* index = nullptr;
+
+    if( git_repository_index( &index, repo ) != GIT_OK )
+    {
+        wxLogDebug( "Failed to get git index: %s", giterr_last()->message );
+        return;
+    }
+
+    git_status_list* status_list = nullptr;
+
+    if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
+    {
+        wxLogDebug( "Failed to get git status list: %s", giterr_last()->message );
+        git_index_free( index );
+        return;
+    }
+
+    auto [ localChanges, remoteChanges ] = m_TreeProject->GitCommon()->GetDifferentFiles();
+
+    size_t count = git_status_list_entrycount( status_list );
+
+    for( size_t ii = 0; ii < count; ++ii )
+    {
+        const git_status_entry* entry = git_status_byindex( status_list, ii );
+        std::string path( entry->head_to_index? entry->head_to_index->old_file.path
+                        : entry->index_to_workdir->old_file.path );
+        wxFileName fn( path );
+        fn.MakeAbsolute( git_repository_workdir( repo ) );
+
+        auto iter = branchMap.find( fn.GetFullPath() );
+
+        if( iter == branchMap.end() )
+            continue;
+
+        // If we are current, don't continue because we still need to check to see if the
+        // current commit is ahead/behind the remote.  If the file is modified/added/deleted,
+        // that is the main status we want to show.
+        if( entry->status == GIT_STATUS_CURRENT )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT ) );
+        }
+        else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED ) );
+            continue;
+        }
+        else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED ) );
+            continue;
+        }
+        else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED ) );
+            continue;
+        }
+
+        // Check if file is up to date with the remote
+        if( localChanges.count( path ) )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD ) );
+            continue;
+        }
+        else if( remoteChanges.count( path ) )
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND ) );
+            continue;
+        }
+        else
+        {
+            m_TreeProject->SetItemState(
+                    iter->second,
+                    static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT ) );
+            continue;
+        }
+    }
+
+    git_status_list_free( status_list );
+    git_index_free( index );
+}
+
+
+void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
+{
+    std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
+
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( repo == nullptr )
+    {
+        wxMessageBox( "The selected directory is not a git project." );
+        return;
+    }
+
+    git_config* config = nullptr;
+    git_repository_config( &config, repo );
+
+    // Read relevant data from the git config
+    const char* authorName = nullptr;
+    const char* authorEmail = nullptr;
+
+    // Read author name
+    git_config_entry* name_c = nullptr;
+    git_config_entry* email_c = nullptr;
+    int authorNameError = git_config_get_entry( &name_c, config, "user.name" );
+
+    if( authorNameError != 0 || name_c == nullptr )
+    {
+        authorName = Pgm().GetCommonSettings()->m_Git.authorName;
+    }
+    else
+    {
+        authorName = name_c->value;
+        git_config_entry_free( name_c );
+    }
+
+    // Read author email
+    int authorEmailError = git_config_get_entry( &email_c, config, "user.email" );
+
+    if( authorEmailError != 0 || email_c == nullptr )
+    {
+        authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
+    }
+    else
+    {
+        authorEmail = email_c->value;
+        git_config_entry_free( email_c );
+    }
+
+    // Free the config object
+    git_config_free( config );
+
+    // Collect modified files in the repository
+    git_status_options status_options = GIT_STATUS_OPTIONS_INIT;
+    status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+    status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+
+    git_status_list* status_list = nullptr;
+    git_status_list_new( &status_list, repo, &status_options );
+
+    std::map<wxString, int> modifiedFiles;
+
+    size_t count = git_status_list_entrycount( status_list );
+
+    std::set<wxString> selected_files;
+
+    for( PROJECT_TREE_ITEM* item : tree_data )
+    {
+        if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
+            selected_files.emplace( item->GetFileName() );
+    }
+
+    for( size_t i = 0; i < count; ++i )
+    {
+        const git_status_entry* entry = git_status_byindex( status_list, i );
+
+        // Check if the file is modified (index or workdir changes)
+        if( entry->status == GIT_STATUS_CURRENT
+            || ( entry->status & ( GIT_STATUS_CONFLICTED | GIT_STATUS_IGNORED ) ) )
+        {
+            continue;
+        }
+
+        wxFileName fn( entry->index_to_workdir->old_file.path );
+        fn.MakeAbsolute( git_repository_workdir( repo ) );
+
+        wxString filePath( entry->index_to_workdir->old_file.path, wxConvUTF8 );
+
+        if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
+        {
+            modifiedFiles.emplace( filePath, entry->status );
+        }
+        else if( selected_files.count( fn.GetFullPath() ) )
+        {
+            modifiedFiles.emplace( filePath, entry->status );
+        }
+    }
+
+    git_status_list_free( status_list );
+
+    // Create a commit dialog
+    DIALOG_GIT_COMMIT dlg( this, repo, authorName, authorEmail, modifiedFiles );
+    auto              ret = dlg.ShowModal();
+
+    if( ret == wxID_OK )
+    {
+        // Commit the changes
+        git_oid     tree_id;
+        git_tree*   tree = nullptr;
+        git_commit* parent = nullptr;
+        git_index*  index = nullptr;
+
+        std::vector<wxString> files = dlg.GetSelectedFiles();
+
+        if( dlg.GetCommitMessage().IsEmpty() )
+        {
+            wxMessageBox( _( "Discarding commit due to empty commit message." ) );
+            return;
+        }
+
+        if( files.empty() )
+        {
+            wxMessageBox( _( "Discarding commit due to empty file selection." ) );
+            return;
+        }
+
+        if( git_repository_index( &index, repo ) != 0 )
+        {
+            wxMessageBox( _( "Failed to get repository index: %s" ), giterr_last()->message );
+            return;
+        }
+
+        for( wxString& file :files )
+        {
+            if( git_index_add_bypath( index, file.mb_str() ) != 0 )
+            {
+                wxMessageBox( _( "Failed to add file to index: %s" ), giterr_last()->message );
+                git_index_free( index );
+                return;
+            }
+        }
+
+        if( git_index_write( index ) != 0 )
+        {
+            wxMessageBox( _( "Failed to write index: %s" ), giterr_last()->message );
+            git_index_free( index );
+            return;
+        }
+
+        if (git_index_write_tree( &tree_id, index ) != 0)
+        {
+            wxMessageBox( _( "Failed to write tree: %s" ), giterr_last()->message );
+            git_index_free( index );
+            return;
+        }
+
+        git_index_free( index );
+
+        if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
+        {
+            wxMessageBox( _( "Failed to lookup tree: %s" ), giterr_last()->message );
+            return;
+        }
+
+        git_reference* headRef = nullptr;
+
+        if( git_repository_head( &headRef, repo ) != 0 )
+        {
+            wxMessageBox( _( "Failed to get HEAD reference: %s" ), giterr_last()->message );
+            git_index_free( index );
+            return;
+        }
+
+        if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
+        {
+            wxMessageBox( _( "Failed to get commit: %s" ), giterr_last()->message );
+            git_reference_free( headRef );
+            git_index_free( index );
+            return;
+        }
+
+        git_reference_free( headRef );
+
+        const wxString& commit_msg = dlg.GetCommitMessage();
+        const wxString& author_name = dlg.GetAuthorName();
+        const wxString& author_email = dlg.GetAuthorEmail();
+
+        git_signature* author = nullptr;
+
+        if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
+        {
+            wxMessageBox( _( "Failed to create author signature: %s" ), giterr_last()->message );
+            return;
+        }
+
+        git_oid           oid;
+        const git_commit* parents[1] = { parent };
+
+        if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
+                           1, parents ) != 0 )
+        {
+            wxMessageBox( _( "Failed to create commit: %s" ), giterr_last()->message );
+            return;
+        }
+
+        git_signature_free( author );
+        git_commit_free( parent );
+        git_tree_free( tree );
+    }
+}
+
+void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
+{
+
+}
+
+
+bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
+{
+    git_index *index;
+    size_t entry_pos;
+
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return false;
+
+    if( git_repository_index( &index, repo ) != 0 )
+        return false;
+
+    // If we successfully find the file in the index, we may not add it to the VCS
+    if( git_index_find( &entry_pos, index, aFile.mb_str() ) == 0 )
+    {
+        git_index_free( index );
+        return false;
+    }
+
+    git_index_free( index );
+    return true;
+}
+
+
+void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_SYNC_HANDLER handler( repo );
+    handler.PerformSync();
+}
+
+
+void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_PULL_HANDLER handler( repo );
+    handler.PerformFetch();
+}
+
+void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
+    handler.PerformResolveConflict();
+}
+
+void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_REVERT_HANDLER handler( repo );
+    handler.PerformRevert();
+}
+
+void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
+{
+    git_repository* repo = m_TreeProject->GetGitRepo();
+
+    if( !repo )
+        return;
+
+    GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
+    handler.PerformRemoveFromIndex();
+}
+
+
+
diff --git a/kicad/project_tree_pane.h b/kicad/project_tree_pane.h
index 2ce7f2b7c7..9a845c630e 100644
--- a/kicad/project_tree_pane.h
+++ b/kicad/project_tree_pane.h
@@ -31,6 +31,7 @@
 #define TREEPRJ_FRAME_H
 
 #include <vector>
+#include <wx/datetime.h>
 #include <wx/fswatcher.h>
 #include <wx/laywin.h>
 #include <wx/treebase.h>
@@ -154,6 +155,87 @@ private:
      */
     void onPaint( wxPaintEvent& aEvent );
 
+    /**
+     * Initialize a new git repository in the current project directory
+    */
+    void onGitInitializeProject( wxCommandEvent& event );
+
+    /**
+     * Commit the current project saved changes to the git repository
+    */
+    void onGitCommit( wxCommandEvent& event );
+
+    /**
+     * Pull the latest changes from the git repository
+    */
+    void onGitPullProject( wxCommandEvent& event );
+
+    /**
+     * Push the current project changes to the git repository
+    */
+    void onGitPushProject( wxCommandEvent& event );
+
+    /**
+     * Switch to a different branch in the git repository
+    */
+    void onGitSwitchBranch( wxCommandEvent& event );
+
+    /**
+     * Compare the current project to a different branch in the git repository
+    */
+    void onGitCompare( wxCommandEvent& event );
+
+    /**
+     * Remove the git repository from the current project directory
+    */
+    void onGitRemoveVCS( wxCommandEvent& event );
+
+    /**
+     * Add a file to the git index
+    */
+    void onGitAddToIndex( wxCommandEvent& event );
+
+    /**
+     * Remove a file from the git index
+    */
+    void onGitRemoveFromIndex( wxCommandEvent& event );
+
+    /**
+     * Sync the current project with the git repository
+    */
+    void onGitSyncProject( wxCommandEvent& event );
+
+    /**
+     * Fetch the latest changes from the git repository
+    */
+    void onGitFetch( wxCommandEvent& event );
+
+    /**
+     * Resolve conflicts in the git repository
+    */
+    void onGitResolveConflict( wxCommandEvent& event );
+
+    /**
+     * Revert the local repository to the last commit
+    */
+    void onGitRevertLocal( wxCommandEvent& event );
+
+    /**
+     * Updates the icons shown in the tree project to reflect the current git status
+    */
+    void updateGitStatusIcons();
+
+    /**
+     * Returns true if the current project has any uncommitted changes
+    */
+    bool hasChangedFiles();
+
+    /**
+     * Returns true if the current project has local commits that have not been pushed to the
+     * remote repository
+    */
+    bool hasLocalCommits();
+
     /**
      * Shutdown the file watcher.  Used when closing to prevent post-free access into the project
      * tree.  (Using the destructor doesn't work as wxWidgets defers destruction in some cases.)
@@ -190,6 +272,12 @@ private:
 
     void onThemeChanged( wxSysColourChangedEvent &aEvent );
 
+    /**
+     * Returns true if the file has already been added to the repository or
+     * false if it has not been added yet.
+    */
+    bool canFileBeAddedToVCS( const wxString& aFilePath );
+
 public:
     KICAD_MANAGER_FRAME*    m_Parent;
     PROJECT_TREE*           m_TreeProject;
@@ -203,6 +291,7 @@ private:
     bool                    m_watcherNeedReset; // true if FileWatcherReset() must be called
                                                 // (during an idle time for instance) after
                                                 // the main loop event handler is started
+    wxDateTime              m_lastGitStatusUpdate;
 
     DECLARE_EVENT_TABLE()
 };
diff --git a/kicad/tools/kicad_manager_actions.cpp b/kicad/tools/kicad_manager_actions.cpp
index 1a58e2d9c4..06083b1d27 100644
--- a/kicad/tools/kicad_manager_actions.cpp
+++ b/kicad/tools/kicad_manager_actions.cpp
@@ -53,6 +53,14 @@ TOOL_ACTION KICAD_MANAGER_ACTIONS::newFromTemplate( TOOL_ACTION_ARGS()
         .Tooltip( _( "Create new project from template" ) )
         .Icon( BITMAPS::new_project_from_template ) );
 
+TOOL_ACTION KICAD_MANAGER_ACTIONS::newFromRepository( TOOL_ACTION_ARGS()
+        .Name( "kicad.Control.newFromRepository" )
+        .Scope( AS_GLOBAL )
+        .LegacyHotkeyName( "Clone Project From Repository" )
+        .MenuText( _( "Clone Project from Repository..." ) )
+        .Tooltip( _( "Clone a project from an existing repository" ) )
+        .Icon( BITMAPS::new_project_from_template ) );
+
 TOOL_ACTION KICAD_MANAGER_ACTIONS::openDemoProject( TOOL_ACTION_ARGS()
         .Name( "kicad.Control.openDemoProject" )
         .Scope( AS_GLOBAL )
diff --git a/kicad/tools/kicad_manager_actions.h b/kicad/tools/kicad_manager_actions.h
index b72cbfc0e9..040013ec8b 100644
--- a/kicad/tools/kicad_manager_actions.h
+++ b/kicad/tools/kicad_manager_actions.h
@@ -33,6 +33,7 @@ class KICAD_MANAGER_ACTIONS : public ACTIONS
 public:
     static TOOL_ACTION newProject;
     static TOOL_ACTION newFromTemplate;
+    static TOOL_ACTION newFromRepository;
     static TOOL_ACTION openDemoProject;
     static TOOL_ACTION openProject;
     static TOOL_ACTION closeProject;
diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp
index 84b115fec6..650dc521ae 100644
--- a/kicad/tools/kicad_manager_control.cpp
+++ b/kicad/tools/kicad_manager_control.cpp
@@ -36,6 +36,8 @@
 #include <tools/kicad_manager_actions.h>
 #include <tools/kicad_manager_control.h>
 #include <dialogs/dialog_template_selector.h>
+#include <dialogs/git/dialog_git_repository.h>
+#include <git/git_clone_handler.h>
 #include <gestfich.h>
 #include <paths.h>
 #include <wx/checkbox.h>
@@ -62,10 +64,12 @@ void KICAD_MANAGER_CONTROL::Reset( RESET_REASON aReason )
 }
 
 
-int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
+wxFileName KICAD_MANAGER_CONTROL::newProjectDirectory( wxString* aFileName )
 {
+    wxString default_filename = aFileName ? *aFileName : wxString();
+
     wxString        default_dir = m_frame->GetMruPath();
-    wxFileDialog    dlg( m_frame, _( "Create New Project" ), default_dir, wxEmptyString,
+    wxFileDialog    dlg( m_frame, _( "Create New Project" ), default_dir, default_filename,
                          ProjectFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
 
     // Add a "Create a new directory" checkbox
@@ -73,7 +77,7 @@ int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
     dlg.SetCustomizeHook( newProjectHook );
 
     if( dlg.ShowModal() == wxID_CANCEL )
-        return -1;
+        return wxFileName();
 
     wxFileName pro( dlg.GetPath() );
 
@@ -107,7 +111,7 @@ int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
                            "Make sure you have write permissions and try again." ),
                         pro.GetPath() );
             DisplayErrorMessage( m_frame, msg );
-            return -1;
+            return wxFileName();
         }
     }
     else if( directory.HasFiles() )
@@ -117,9 +121,21 @@ int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
                           "Do you want to continue?" );
 
         if( !IsOK( m_frame, msg ) )
-            return -1;
+            return wxFileName();
     }
 
+    return pro;
+}
+
+
+int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
+{
+
+    wxFileName pro = newProjectDirectory();
+
+    if( !pro.IsOk() )
+        return -1;
+
     m_frame->CreateNewProject( pro );
     m_frame->LoadProject( pro );
 
@@ -127,6 +143,68 @@ int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent )
 }
 
 
+int KICAD_MANAGER_CONTROL::NewFromRepository( const TOOL_EVENT& aEvent )
+{
+    DIALOG_GIT_REPOSITORY dlg( m_frame, nullptr );
+
+    dlg.SetTitle( _( "Clone Project from Git Repository" ) );
+
+    int ret = dlg.ShowModal();
+
+    if( ret != wxID_OK )
+        return -1;
+
+    wxString   repodest = dlg.GetRepoName();
+    wxFileName pro = newProjectDirectory( &repodest );
+
+    if( !pro.IsOk() )
+        return -1;
+
+    GIT_CLONE_HANDLER cloneHandler;
+
+    cloneHandler.SetURL( dlg.GetRepoURL() );
+    cloneHandler.SetClonePath( pro.GetPath() );
+    cloneHandler.SetConnType( dlg.GetRepoType() );
+    cloneHandler.SetUsername( dlg.GetUsername() );
+    cloneHandler.SetPassword( dlg.GetPassword() );
+    cloneHandler.SetSSHKey( dlg.GetRepoSSHPath() );
+
+    cloneHandler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( m_frame, _( "Cloning Repository" ), 1 ) );
+
+    if( !cloneHandler.PerformClone() )
+    {
+        DisplayErrorMessage( m_frame, cloneHandler.GetErrorString() );
+        return -1;
+    }
+
+    std::vector<wxString> projects = cloneHandler.GetProjectDirs();
+
+    if( projects.empty() )
+    {
+        DisplayErrorMessage( m_frame, _( "No project files were found in the repository." ) );
+        return -1;
+    }
+
+    // Currently, we pick the first project file we find in the repository.
+    // TODO: Look into spare checkout to allow the user to pick a partial repository
+    wxString dest = pro.GetPath() + wxFileName::GetPathSeparator() + projects.front();
+    m_frame->LoadProject( dest );
+
+    Prj().GetLocalSettings().m_GitRepoPassword = dlg.GetPassword();
+    Prj().GetLocalSettings().m_GitRepoUsername = dlg.GetUsername();
+    Prj().GetLocalSettings().m_GitSSHKey = dlg.GetRepoSSHPath();
+
+    if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
+        Prj().GetLocalSettings().m_GitRepoType = "ssh";
+    else if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
+        Prj().GetLocalSettings().m_GitRepoType = "https";
+    else
+        Prj().GetLocalSettings().m_GitRepoType = "local";
+
+    return 0;
+}
+
+
 int KICAD_MANAGER_CONTROL::NewFromTemplate( const TOOL_EVENT& aEvent )
 {
     DIALOG_TEMPLATE_SELECTOR* ps = new DIALOG_TEMPLATE_SELECTOR( m_frame );
@@ -874,6 +952,7 @@ void KICAD_MANAGER_CONTROL::setTransitions()
 {
     Go( &KICAD_MANAGER_CONTROL::NewProject,         KICAD_MANAGER_ACTIONS::newProject.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::NewFromTemplate,    KICAD_MANAGER_ACTIONS::newFromTemplate.MakeEvent() );
+    Go( &KICAD_MANAGER_CONTROL::NewFromRepository,  KICAD_MANAGER_ACTIONS::newFromRepository.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::OpenDemoProject,    KICAD_MANAGER_ACTIONS::openDemoProject.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::OpenProject,        KICAD_MANAGER_ACTIONS::openProject.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::CloseProject,       KICAD_MANAGER_ACTIONS::closeProject.MakeEvent() );
diff --git a/kicad/tools/kicad_manager_control.h b/kicad/tools/kicad_manager_control.h
index badbfd4434..1ab7964bb5 100644
--- a/kicad/tools/kicad_manager_control.h
+++ b/kicad/tools/kicad_manager_control.h
@@ -46,6 +46,7 @@ public:
 
     int NewProject( const TOOL_EVENT& aEvent );
     int NewFromTemplate( const TOOL_EVENT& aEvent );
+    int NewFromRepository( const TOOL_EVENT& aEvent );
     int OpenProject( const TOOL_EVENT& aEvent );
     int OpenDemoProject( const TOOL_EVENT& aEvent );
     int CloseProject( const TOOL_EVENT& aEvent );
@@ -77,6 +78,9 @@ private:
     std::mutex m_loading;
 
     int openProject( const wxString& aDefaultDir );
+
+    wxFileName newProjectDirectory( wxString* aFileName = nullptr );
+
 };
 
 #endif
diff --git a/libs/core/include/core/kicad_algo.h b/libs/core/include/core/kicad_algo.h
index 428d94e81e..fc8f08a556 100644
--- a/libs/core/include/core/kicad_algo.h
+++ b/libs/core/include/core/kicad_algo.h
@@ -206,6 +206,37 @@ T clamp( T min, T value, T max )
     return std::max( min, std::min( value, max ) );
 }
 
+/**
+ * @brief Returns the length of the longest common subset of values between two containers.
+*/
+template <class _Container>
+size_t longest_common_subset( const _Container& __c1, const _Container& __c2 )
+{
+    size_t __c1_size = __c1.size();
+    size_t __c2_size = __c2.size();
+
+    if( __c1_size == 0 || __c2_size == 0 )
+        return 0;
+
+    // Create a 2D table to store the lengths of common subsets
+    std::vector<std::vector<size_t>> table( __c1_size + 1, std::vector<size_t>( __c2_size + 1, 0 ) );
+
+    size_t longest = 0;
+
+    for( size_t i = 1; i <= __c1_size; ++i )
+    {
+        for( size_t j = 1; j <= __c2_size; ++j )
+        {
+            if( __c1[i - 1] == __c2[j - 1] )
+            {
+                table[i][j] = table[i - 1][j - 1] + 1;
+                longest = std::max( longest, static_cast<size_t>( table[i][j] ) );
+            }
+        }
+    }
+
+    return longest;
+}
 
 } // namespace alg
 
diff --git a/libs/kiplatform/CMakeLists.txt b/libs/kiplatform/CMakeLists.txt
index 60219bfecf..eda4aabdb9 100644
--- a/libs/kiplatform/CMakeLists.txt
+++ b/libs/kiplatform/CMakeLists.txt
@@ -14,6 +14,7 @@ if( APPLE )
         osx/environment.mm
         osx/io.mm
         osx/policy.mm
+        osx/secrets.mm
         osx/ui.mm
         )
 
@@ -30,6 +31,7 @@ elseif( WIN32 )
         msw/environment.cpp
         msw/io.cpp
         msw/policy.cpp
+        msw/secrets.cpp
         msw/ui.cpp
         )
 
@@ -46,6 +48,7 @@ elseif( UNIX )
         gtk/environment.cpp
         gtk/io.cpp
         gtk/policy.cpp
+        gtk/secrets.cpp
         gtk/ui.cpp
         )
 
@@ -61,6 +64,11 @@ elseif( UNIX )
     link_directories( ${GTK3_LIBRARY_DIRS} )
     add_definitions( ${GTK3_CFLAGS_OTHER} )
 
+    # Detect the secret library and configure it
+    pkg_check_modules(secret REQUIRED libsecret-1)
+    include_directories( SYSTEM ${secret_INCLUDE_DIRS} )
+    list( APPEND PLATFORM_LIBS ${secret_LIBRARIES} )
+
     if( KICAD_WAYLAND )
         find_package(Wayland COMPONENTS Client REQUIRED)
 
diff --git a/libs/kiplatform/gtk/secrets.cpp b/libs/kiplatform/gtk/secrets.cpp
new file mode 100644
index 0000000000..eab664d2bb
--- /dev/null
+++ b/libs/kiplatform/gtk/secrets.cpp
@@ -0,0 +1,84 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+*
+* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+*
+* This program is free software: you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation, either version 3 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <kiplatform/secrets.h>
+
+#include <libsecret/secret.h>
+
+namespace KIPLATFORM
+{
+    namespace SECRETS
+    {
+        static const SecretSchema schema =
+        {
+            "org.kicad.kicad", SECRET_SCHEMA_NONE,
+            {
+                { "service", SECRET_SCHEMA_ATTRIBUTE_STRING },
+                { "key", SECRET_SCHEMA_ATTRIBUTE_STRING },
+                { nullptr, SECRET_SCHEMA_ATTRIBUTE_STRING }
+            }
+        };
+
+        bool StoreSecret( const wxString& aService, const wxString& aKey, const wxString& aSecret )
+        {
+            GError* error = nullptr;
+            wxString display = aService + ":" + aKey;
+
+            secret_password_store_sync( &schema,
+                                        SECRET_COLLECTION_DEFAULT,
+                                        display.mb_str(),   // Display name
+                                        aSecret.mb_str(),   // Secret value
+                                        nullptr,
+                                        &error,
+                                        "service", aService.ToStdString().c_str(),
+                                        "key", aKey.ToStdString().c_str(),
+                                        nullptr );
+
+            if( error )
+            {
+                g_error_free( error );
+                return false;
+            }
+
+            return true;
+        }
+
+        bool GetSecret( const wxString& aService, const wxString& aKey, wxString& aSecret )
+        {
+            GError* error = nullptr;
+            gchar* secret = secret_password_lookup_sync( &schema,
+                                                         nullptr,
+                                                         &error,
+                                                         "service", aService.ToStdString().c_str(),
+                                                         "key", aKey.ToStdString().c_str(),
+                                                         nullptr );
+
+            if( error )
+            {
+                g_error_free( error );
+                return false;
+            }
+
+            aSecret = secret;
+            g_free( secret );
+
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/kiplatform/include/kiplatform/secrets.h b/libs/kiplatform/include/kiplatform/secrets.h
new file mode 100644
index 0000000000..cabdf31496
--- /dev/null
+++ b/libs/kiplatform/include/kiplatform/secrets.h
@@ -0,0 +1,37 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+*
+* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+*
+* This program is free software: you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation, either version 3 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef KIPLATFORM_SECRETS_H_
+#define KIPLATFORM_SECRETS_H_
+
+#include <wx/string.h>
+
+namespace KIPLATFORM
+{
+    namespace SECRETS
+    {
+
+        bool StoreSecret( const wxString& aService, const wxString& aKey, const wxString& aSecret );
+
+        bool GetSecret( const wxString& aService, const wxString& aKey, wxString& aSecret );
+
+    }
+}
+
+#endif // KIPLATFORM_SECRETS_H_
\ No newline at end of file
diff --git a/libs/kiplatform/msw/secrets.cpp b/libs/kiplatform/msw/secrets.cpp
new file mode 100644
index 0000000000..0e87d4f0fa
--- /dev/null
+++ b/libs/kiplatform/msw/secrets.cpp
@@ -0,0 +1,60 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+*
+* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+*
+* This program is free software: you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation, either version 3 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <kiplatform/secrets.h>
+
+#include <windows.h>
+#include <wincred.h>
+#include <wx/string.h>
+
+namespace KIPLATFORM
+{
+    namespace SECRETS
+    {
+        bool StoreSecret( const wxString& aService, const wxString& aKey, const wxString& aSecret )
+        {
+            wxString display = aService + L":" + aKey;
+
+            CREDENTIALW cred = { 0 };
+            cred.Type = CRED_TYPE_GENERIC;
+            cred.TargetName = (LPWSTR)display.wc_str();
+            cred.CredentialBlobSize = (DWORD)aSecret.size();
+            cred.CredentialBlob = (LPBYTE)aSecret.c_str();
+            cred.Persist = CRED_PERSIST_USER;
+
+            return CredWriteW( &cred, 0 );
+        }
+
+        bool GetSecret( const wxString& aService, const wxString& aKey, wxString& aSecret )
+        {
+            wxString display = aService + ":" + aKey;
+
+            CREDENTIALW* cred = nullptr;
+            bool result = CredReadW( display.wc_str(), CRED_TYPE_GENERIC, 0, &cred );
+
+            if( result )
+            {
+                aSecret = wxString( (char*)cred->CredentialBlob, cred->CredentialBlobSize );
+                CredFree( cred );
+            }
+
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/kiplatform/osx/secrets.mm b/libs/kiplatform/osx/secrets.mm
new file mode 100644
index 0000000000..111652af4d
--- /dev/null
+++ b/libs/kiplatform/osx/secrets.mm
@@ -0,0 +1,75 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+*
+* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+*
+* This program is free software: you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation, either version 3 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <kiplatform/secrets.h>
+
+#import <Security/Security.h>
+
+bool KIPLATFORM::SECRETS::StoreSecret( const wxString& aService, const wxString& aKey, const wxString& aValue )
+{
+    SecKeychainItemRef itemRef = NULL;
+
+    OSStatus status = SecKeychainFindGenericPassword( NULL, aService.length(), aService.utf8_str(),
+                                                      aKey.length(), aKey.utf8_str(),
+                                                      NULL, NULL, &itemRef );
+
+    if( status == errSecItemNotFound )
+    {
+        status = SecKeychainAddGenericPassword( NULL, aService.length(), aService.utf8_str(),
+                                                aKey.length(), aKey.utf8_str(),
+                                                aValue.length(), aValue.utf8_str(),
+                                                NULL );
+
+        CFRelease( itemRef );
+    }
+    else if( status == errSecSuccess )
+    {
+        status = SecKeychainItemModifyAttributesAndData( itemRef, NULL, aValue.length(), aValue.utf8_str() );
+    }
+
+
+    return status == errSecSuccess;
+}
+
+bool KIPLATFORM::SECRETS::GetSecret( const wxString& aService, const wxString& aKey, wxString& aValue )
+{
+    SecKeychainItemRef itemRef = NULL;
+
+    OSStatus status = SecKeychainFindGenericPassword( NULL, aService.length(), aService.utf8_str(),
+                                                      aKey.length(), aKey.utf8_str(),
+                                                      NULL, NULL, &itemRef );
+
+    if( status == errSecSuccess )
+    {
+        UInt32 length;
+        char* data;
+
+        status = SecKeychainItemCopyAttributesAndData( itemRef, NULL, NULL, NULL, &length, (void**)&data );
+
+        if( status == errSecSuccess )
+        {
+            aValue = wxString::FromUTF8( data, length );
+            SecKeychainItemFreeAttributesAndData( NULL, data );
+        }
+
+        CFRelease( itemRef );
+    }
+
+    return status == errSecSuccess;
+}
\ No newline at end of file
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 45b92c97e7..4256e4eb28 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -378,10 +378,15 @@ set( PCBNEW_CLASS_SRCS
 
     )
 
+set( PCBNEW_GIT_SRCS
+    git/kigit_pcb_merge.cpp
+    )
+
 set( PCBNEW_SRCS
     ${PCBNEW_MICROWAVE_SRCS}
     ${PCBNEW_CLASS_SRCS}
     ${PCBNEW_DIALOGS}
+    ${PCBNEW_GIT_SRCS}
     )
 
 # extra sources from common
@@ -644,6 +649,7 @@ target_link_libraries( pcbnew_kiface_objects
         rectpack2d
         gzip-hpp
         Boost::boost
+        git2
         ZLIB::ZLIB
         ${OCC_LIBRARIES}
     )
diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp
index 07930e75d2..c8374a0a1d 100644
--- a/pcbnew/board.cpp
+++ b/pcbnew/board.cpp
@@ -2584,21 +2584,85 @@ void BOARD::ConvertBrdLayerToPolygonalContours( PCB_LAYER_ID aLayer,
 }
 
 
-const std::set<BOARD_ITEM*> BOARD::GetItemSet()
+const BOARD_ITEM_SET BOARD::GetItemSet()
 {
-    std::set<BOARD_ITEM*> items;
+    BOARD_ITEM_SET items;
 
-#define INSERT_ITEMS( collection )                                                                 \
-    for( BOARD_ITEM * item : collection )                                                          \
-        items.insert( item );
-
-    INSERT_ITEMS( m_tracks )
-    INSERT_ITEMS( m_footprints )
-    INSERT_ITEMS( m_drawings )
-    INSERT_ITEMS( m_zones )
-    INSERT_ITEMS( m_markers )
-
-#undef INSERT_ITEMS
+    std::copy( m_tracks.begin(), m_tracks.end(), std::inserter( items, items.end() ) );
+    std::copy( m_zones.begin(), m_zones.end(), std::inserter( items, items.end() ) );
+    std::copy( m_footprints.begin(), m_footprints.end(), std::inserter( items, items.end() ) );
+    std::copy( m_drawings.begin(), m_drawings.end(), std::inserter( items, items.end() ) );
+    std::copy( m_markers.begin(), m_markers.end(), std::inserter( items, items.end() ) );
+    std::copy( m_groups.begin(), m_groups.end(), std::inserter( items, items.end() ) );
 
     return items;
 }
+
+
+bool BOARD::operator==( const BOARD_ITEM& aItem ) const
+{
+    if( aItem.Type() != Type() )
+        return false;
+
+    const BOARD& other = static_cast<const BOARD&>( aItem );
+
+    if( m_designSettings != other.m_designSettings )
+        return false;
+
+    if( m_NetInfo.GetNetCount() != other.m_NetInfo.GetNetCount() )
+        return false;
+
+    const NETNAMES_MAP& thisNetNames = m_NetInfo.NetsByName();
+    const NETNAMES_MAP& otherNetNames = other.m_NetInfo.NetsByName();
+
+    for( auto it1 = thisNetNames.begin(), it2 = otherNetNames.begin();
+         it1 != thisNetNames.end() && it2 != otherNetNames.end(); ++it1, ++it2 )
+    {
+        // We only compare the names in order here, not the index values
+        // as the index values are auto-generated and the names are not.
+        if( it1->first != it2->first )
+            return false;
+    }
+
+    if( m_properties.size() != other.m_properties.size() )
+        return false;
+
+    for( auto it1 = m_properties.begin(), it2 = other.m_properties.begin();
+         it1 != m_properties.end() && it2 != other.m_properties.end(); ++it1, ++it2 )
+    {
+        if( *it1 != *it2 )
+            return false;
+    }
+
+    if( m_paper.GetCustomHeightMils() != other.m_paper.GetCustomHeightMils() )
+        return false;
+
+    if( m_paper.GetCustomWidthMils() != other.m_paper.GetCustomWidthMils() )
+        return false;
+
+    if( m_paper.GetSizeMils() != other.m_paper.GetSizeMils() )
+        return false;
+
+    if( m_paper.GetPaperId() != other.m_paper.GetPaperId() )
+        return false;
+
+    if( m_paper.GetWxOrientation() != other.m_paper.GetWxOrientation() )
+        return false;
+
+    for( int ii = 0; !m_titles.GetComment( ii ).empty(); ++ii )
+    {
+        if( m_titles.GetComment( ii ) != other.m_titles.GetComment( ii ) )
+            return false;
+    }
+
+    wxArrayString ourVars;
+    m_titles.GetContextualTextVars( &ourVars );
+
+    wxArrayString otherVars;
+    other.m_titles.GetContextualTextVars( &otherVars );
+
+    if( ourVars != otherVars )
+        return false;
+
+    return true;
+}
\ No newline at end of file
diff --git a/pcbnew/board.h b/pcbnew/board.h
index 8ba1a2b50b..1f4b3104b9 100644
--- a/pcbnew/board.h
+++ b/pcbnew/board.h
@@ -254,6 +254,10 @@ public:
     virtual void OnBoardRatsnestChanged( BOARD& aBoard ) { }
 };
 
+/**
+ * Set of BOARD_ITEMs ordered by UUID.
+ */
+typedef std::set<BOARD_ITEM*, CompareByUuid> BOARD_ITEM_SET;
 
 /**
  * Flags to specify how the board is being used.
@@ -326,7 +330,7 @@ public:
     MARKERS& Markers() { return m_markers; }
     const MARKERS& Markers() const { return m_markers; }
 
-    const std::set<BOARD_ITEM*> GetItemSet();
+    const BOARD_ITEM_SET GetItemSet();
 
     /**
      * The groups must maintain the following invariants. These are checked by
@@ -936,6 +940,16 @@ public:
      */
     void SynchronizeProperties();
 
+    /**
+     * Return the Similarity.  Because we compare board to board, we just return 1.0 here
+    */
+    double Similarity( const BOARD_ITEM& aOther ) const override
+    {
+        return 1.0;
+    }
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
     wxString GetClass() const override
     {
         return wxT( "BOARD" );
diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp
index b7790b75d1..621ad05de5 100644
--- a/pcbnew/footprint.cpp
+++ b/pcbnew/footprint.cpp
@@ -2899,6 +2899,77 @@ bool FOOTPRINT::HasThroughHolePads() const
 }
 
 
+bool FOOTPRINT::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != PCB_FOOTPRINT_T )
+        return false;
+
+    const FOOTPRINT& other = static_cast<const FOOTPRINT&>( aOther );
+
+    if( m_pads.size() != other.m_pads.size() )
+        return false;
+
+    for( size_t ii = 0; ii < m_pads.size(); ++ii )
+    {
+        if( !( *m_pads[ii] == *other.m_pads[ii] ) )
+            return false;
+    }
+
+    if( m_drawings.size() != other.m_drawings.size() )
+        return false;
+
+    for( size_t ii = 0; ii < m_drawings.size(); ++ii )
+    {
+        if( !( *m_drawings[ii] == *other.m_drawings[ii] ) )
+            return false;
+    }
+
+    if( m_zones.size() != other.m_zones.size() )
+        return false;
+
+    for( size_t ii = 0; ii < m_zones.size(); ++ii )
+    {
+        if( !( *m_zones[ii] == *other.m_zones[ii] ) )
+            return false;
+    }
+
+    if( m_fields.size() != other.m_fields.size() )
+        return false;
+
+    for( size_t ii = 0; ii < m_fields.size(); ++ii )
+    {
+        if( !( *m_fields[ii] == *other.m_fields[ii] ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double FOOTPRINT::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != PCB_FOOTPRINT_T )
+        return 0.0;
+
+    const FOOTPRINT& other = static_cast<const FOOTPRINT&>( aOther );
+
+    double similarity = 1.0;
+
+    for( size_t ii = 0; ii < m_pads.size(); ++ii )
+    {
+        const PAD* pad = m_pads[ii];
+        const PAD* otherPad = other.FindPadByNumber( pad->GetNumber() );
+
+        if( !otherPad )
+            continue;
+
+        similarity *= pad->Similarity( *otherPad );
+    }
+
+    return similarity;
+}
+
+
 #define TEST( a, b ) { if( a != b ) return a < b; }
 #define TEST_PT( a, b ) { if( a.x != b.x ) return a.x < b.x; if( a.y != b.y ) return a.y < b.y; }
 
diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h
index 2c74b4ba20..d33bd44c5e 100644
--- a/pcbnew/footprint.h
+++ b/pcbnew/footprint.h
@@ -919,6 +919,10 @@ public:
     std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
                                               FLASHING aFlash = FLASHING::DEFAULT ) const override;
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/git/kigit_pcb_merge.cpp b/pcbnew/git/kigit_pcb_merge.cpp
new file mode 100644
index 0000000000..94506f3534
--- /dev/null
+++ b/pcbnew/git/kigit_pcb_merge.cpp
@@ -0,0 +1,180 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "kigit_pcb_merge.h"
+
+#include <plugins/kicad/pcb_plugin.h>
+#include <plugins/kicad/pcb_parser.h>
+#include <richio.h>
+
+#include <board.h>
+
+#include <git/kicad_git_blob_reader.h>
+
+void KIGIT_PCB_MERGE::findSetDifferences( const BOARD_ITEM_SET& aAncestorSet, const BOARD_ITEM_SET& aOtherSet,
+                                          std::vector<BOARD_ITEM*>& aAdded, std::vector<BOARD_ITEM*>& aRemoved,
+                                          std::vector<BOARD_ITEM*>& aChanged )
+{
+    auto it1 = aAncestorSet.begin();
+    auto it2 = aOtherSet.begin();
+
+    while( it1 != aAncestorSet.end() && it2 != aOtherSet.end() )
+    {
+        BOARD_ITEM* item1 = *it1;
+        BOARD_ITEM* item2 = *it2;
+
+        if( item1->m_Uuid < item2->m_Uuid )
+        {
+            aRemoved.push_back( item1 );
+            ++it1;
+        }
+        else if( item1->m_Uuid > item2->m_Uuid )
+        {
+            aAdded.push_back( item2 );
+            ++it2;
+        }
+        else
+        {
+            if( !( *item1 == *item2 ) )
+                aChanged.push_back( item1 );
+
+            ++it1;
+            ++it2;
+        }
+    }
+}
+KIGIT_PCB_MERGE_DIFFERENCES KIGIT_PCB_MERGE::compareBoards( BOARD* aAncestor, BOARD* aOther )
+{
+    KIGIT_PCB_MERGE_DIFFERENCES differences;
+
+    const auto ancestor_set = aAncestor->GetItemSet();
+    const auto other_set = aOther->GetItemSet();
+
+    findSetDifferences( ancestor_set, other_set, differences.m_added, differences.m_removed, differences.m_changed );
+
+    return differences;
+}
+
+int KIGIT_PCB_MERGE::Merge()
+{
+    git_repository* repo = git_merge_driver_source_repo( m_mergeDriver );
+    const git_index_entry* ancestor = git_merge_driver_source_ancestor( m_mergeDriver );
+    const git_index_entry* ours = git_merge_driver_source_ours( m_mergeDriver );
+    const git_index_entry* theirs = git_merge_driver_source_theirs( m_mergeDriver );
+
+    // Read ancestor index entry into a buffer
+
+    git_blob* ancestor_blob;
+    git_blob* ours_blob;
+    git_blob* theirs_blob;
+
+    if( git_blob_lookup( &ancestor_blob, repo, &ancestor->id ) != 0 )
+    {
+        return GIT_ENOTFOUND;
+    }
+
+    if( git_blob_lookup( &ours_blob, repo, &ours->id ) != 0 )
+    {
+        git_blob_free( ancestor_blob );
+        return GIT_ENOTFOUND;
+    }
+
+    if( git_blob_lookup( &theirs_blob, repo, &theirs->id ) != 0 )
+    {
+        git_blob_free( ancestor_blob );
+        git_blob_free( ours_blob );
+        return GIT_ENOTFOUND;
+    }
+
+    // Get the raw data from the blobs
+    BLOB_READER ancestor_reader( ancestor_blob );
+    PCB_PARSER ancestor_parser( &ancestor_reader, nullptr, nullptr );
+    BLOB_READER ours_reader( ours_blob );
+    PCB_PARSER ours_parser( &ours_reader, nullptr, nullptr );
+    BLOB_READER theirs_reader( theirs_blob );
+    PCB_PARSER theirs_parser( &theirs_reader, nullptr, nullptr );
+
+    std::unique_ptr<BOARD> ancestor_board;
+    std::unique_ptr<BOARD> ours_board;
+    std::unique_ptr<BOARD> theirs_board;
+
+    try
+    {
+        ancestor_board.reset( static_cast<BOARD*>( ancestor_parser.Parse() ) );
+        ours_board.reset( static_cast<BOARD*>( ours_parser.Parse() ) );
+        theirs_board.reset( static_cast<BOARD*>( theirs_parser.Parse() ) );
+    }
+    catch(const IO_ERROR&)
+    {
+        git_blob_free( ancestor_blob );
+        git_blob_free( ours_blob );
+        git_blob_free( theirs_blob );
+        return GIT_EUSER;
+    }
+
+    git_blob_free( ancestor_blob );
+    git_blob_free( ours_blob );
+    git_blob_free( theirs_blob );
+
+    // Parse the differences between the ancestor and ours
+    KIGIT_PCB_MERGE_DIFFERENCES ancestor_ours_differences = compareBoards( ancestor_board.get(), ours_board.get() );
+    KIGIT_PCB_MERGE_DIFFERENCES ancestor_theirs_differences = compareBoards( ancestor_board.get(), theirs_board.get() );
+
+    // Find the items that we modified and they deleted
+    std::set_intersection( ancestor_ours_differences.m_changed.begin(), ancestor_ours_differences.m_changed.end(),
+                           ancestor_theirs_differences.m_removed.begin(), ancestor_theirs_differences.m_removed.end(),
+                           std::inserter( we_modified_they_deleted, we_modified_they_deleted.begin() ) );
+
+    // Find the items that they modified and we deleted
+    std::set_intersection( ancestor_theirs_differences.m_changed.begin(), ancestor_theirs_differences.m_changed.end(),
+                           ancestor_ours_differences.m_removed.begin(), ancestor_ours_differences.m_removed.end(),
+                           std::inserter( they_modified_we_deleted, they_modified_we_deleted.begin() ) );
+
+    // Find the items that both we and they modified
+    std::set_intersection( ancestor_ours_differences.m_changed.begin(), ancestor_ours_differences.m_changed.end(),
+                           ancestor_theirs_differences.m_changed.begin(), ancestor_theirs_differences.m_changed.end(),
+                           std::inserter( both_modified, both_modified.begin() ) );
+
+    return 0;
+}
+
+
+std::unique_ptr<BOARD> readBoard( wxString& aFilename )
+{
+    // Take input from stdin
+    FILE_LINE_READER reader( aFilename );
+
+    PCB_PARSER             parser( &reader, nullptr, nullptr );
+    std::unique_ptr<BOARD> board;
+
+    try
+    {
+        board.reset( static_cast<BOARD*>( parser.Parse() ) );
+    }
+    catch( const IO_ERROR& )
+    {
+    }
+
+    return board;
+}
+
diff --git a/pcbnew/git/kigit_pcb_merge.h b/pcbnew/git/kigit_pcb_merge.h
new file mode 100644
index 0000000000..46a07c02f9
--- /dev/null
+++ b/pcbnew/git/kigit_pcb_merge.h
@@ -0,0 +1,85 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef KIGIT_PCB_MERGE_H
+#define KIGIT_PCB_MERGE_H
+#include <git2.h>
+#include <git2/sys/merge.h>
+
+#include <memory>
+#include <wx/string.h>
+
+#include <board.h>
+
+typedef struct KIGIT_PCB_MERGE_DIFFERENCES
+{
+    std::vector<BOARD_ITEM*> m_added;
+    std::vector<BOARD_ITEM*> m_removed;
+    std::vector<BOARD_ITEM*> m_changed;
+} KIGIT_PCB_MERGE_DIFFERENCES;
+
+class KIGIT_PCB_MERGE
+{
+    public:
+        KIGIT_PCB_MERGE( git_merge_driver_source* aSource, git_buf* aBuf ) : m_mergeDriver( aSource ), m_result( aBuf )
+        {}
+
+        virtual ~KIGIT_PCB_MERGE();
+
+        int Merge();
+
+        std::set<BOARD_ITEM*>& GetWeModifiedTheyDeleted()
+        {
+            return we_modified_they_deleted;
+        }
+
+        std::set<BOARD_ITEM*>& GetTheyModifiedWeDeleted()
+        {
+            return they_modified_we_deleted;
+        }
+
+        std::set<BOARD_ITEM*>& GetBothModified()
+        {
+            return both_modified;
+        }
+
+    protected:
+        std::unique_ptr<BOARD> readBoard( wxString& aFilename );
+        KIGIT_PCB_MERGE_DIFFERENCES compareBoards( BOARD* aAncestor, BOARD* aOther );
+        void findSetDifferences( const BOARD_ITEM_SET& aAncestorSet, const BOARD_ITEM_SET& aOtherSet,
+                                 std::vector<BOARD_ITEM*>& aAdded, std::vector<BOARD_ITEM*>& aRemoved,
+                                 std::vector<BOARD_ITEM*>& aChanged );
+
+    private:
+
+        git_merge_driver_source* m_mergeDriver;
+        git_buf* m_result;
+
+    std::set<BOARD_ITEM*> we_modified_they_deleted;
+    std::set<BOARD_ITEM*> they_modified_we_deleted;
+    std::set<BOARD_ITEM*> both_modified;
+};
+
+
+
+#endif // KIGIT_PCB_MERGE_H
\ No newline at end of file
diff --git a/pcbnew/netinfo.h b/pcbnew/netinfo.h
index f86ae1da53..045804cd6e 100644
--- a/pcbnew/netinfo.h
+++ b/pcbnew/netinfo.h
@@ -171,6 +171,16 @@ public:
 
     bool Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override
+    {
+        return 0.0;
+    }
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override
+    {
+        return 0.0;
+    }
+
 private:
     friend class NETINFO_LIST;
 
diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp
index 249be9b65e..98dab89f2f 100644
--- a/pcbnew/pad.cpp
+++ b/pcbnew/pad.cpp
@@ -1684,6 +1684,186 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer,
 }
 
 
+bool PAD::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    if( m_parent->m_Uuid != aOther.GetParent()->m_Uuid )
+        return false;
+
+    const PAD& other = static_cast<const PAD&>( aOther );
+
+    if( GetShape() != other.GetShape() )
+        return false;
+
+    if( GetPosition() != other.GetPosition() )
+        return false;
+
+    if( GetAttribute() != other.GetAttribute() )
+        return false;
+
+    if( GetSize() != other.GetSize() )
+        return false;
+
+    if( GetOffset() != other.GetOffset() )
+        return false;
+
+    if( GetDrillSize() != other.GetDrillSize() )
+        return false;
+
+    if( GetDrillShape() != other.GetDrillShape() )
+        return false;
+
+    if( GetRoundRectRadiusRatio() != other.GetRoundRectRadiusRatio() )
+        return false;
+
+    if( GetChamferRectRatio() != other.GetChamferRectRatio() )
+        return false;
+
+    if( GetChamferPositions() != other.GetChamferPositions() )
+        return false;
+
+    if( GetOrientation() != other.GetOrientation() )
+        return false;
+
+    if( GetZoneConnection() != other.GetZoneConnection() )
+        return false;
+
+    if( GetThermalSpokeWidth() != other.GetThermalSpokeWidth() )
+        return false;
+
+    if( GetThermalSpokeAngle() != other.GetThermalSpokeAngle() )
+        return false;
+
+    if( GetThermalGap() != other.GetThermalGap() )
+        return false;
+
+    if( GetCustomShapeInZoneOpt() != other.GetCustomShapeInZoneOpt() )
+        return false;
+
+    if( GetPrimitives().size() != other.GetPrimitives().size() )
+        return false;
+
+    for( size_t ii = 0; ii < GetPrimitives().size(); ii++ )
+    {
+        if( GetPrimitives()[ii] != other.GetPrimitives()[ii] )
+            return false;
+    }
+
+    if( GetAnchorPadShape() != other.GetAnchorPadShape() )
+        return false;
+
+    if( GetLocalClearance() != other.GetLocalClearance() )
+        return false;
+
+    if( GetLocalSolderMaskMargin() != other.GetLocalSolderMaskMargin() )
+        return false;
+
+    if( GetLocalSolderPasteMargin() != other.GetLocalSolderPasteMargin() )
+        return false;
+
+    if( GetLocalSolderPasteMarginRatio() != other.GetLocalSolderPasteMarginRatio() )
+        return false;
+
+    if( GetLocalSpokeWidthOverride() != other.GetLocalSpokeWidthOverride() )
+        return false;
+
+    if( GetLayerSet() != other.GetLayerSet() )
+        return false;
+
+    return true;
+}
+
+
+double PAD::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    if( m_parent->m_Uuid != aOther.GetParent()->m_Uuid )
+        return 0.0;
+
+    const PAD& other = static_cast<const PAD&>( aOther );
+
+    double similarity = 1.0;
+
+    if( GetShape() != other.GetShape() )
+        similarity *= 0.9;
+
+    if( GetPosition() != other.GetPosition() )
+        similarity *= 0.9;
+
+    if( GetAttribute() != other.GetAttribute() )
+        similarity *= 0.9;
+
+    if( GetSize() != other.GetSize() )
+        similarity *= 0.9;
+
+    if( GetOffset() != other.GetOffset() )
+        similarity *= 0.9;
+
+    if( GetDrillSize() != other.GetDrillSize() )
+        similarity *= 0.9;
+
+    if( GetDrillShape() != other.GetDrillShape() )
+        similarity *= 0.9;
+
+    if( GetRoundRectRadiusRatio() != other.GetRoundRectRadiusRatio() )
+        similarity *= 0.9;
+
+    if( GetChamferRectRatio() != other.GetChamferRectRatio() )
+        similarity *= 0.9;
+
+    if( GetChamferPositions() != other.GetChamferPositions() )
+        similarity *= 0.9;
+
+    if( GetOrientation() != other.GetOrientation() )
+        similarity *= 0.9;
+
+    if( GetZoneConnection() != other.GetZoneConnection() )
+        similarity *= 0.9;
+
+    if( GetThermalSpokeWidth() != other.GetThermalSpokeWidth() )
+        similarity *= 0.9;
+
+    if( GetThermalSpokeAngle() != other.GetThermalSpokeAngle() )
+        similarity *= 0.9;
+
+    if( GetThermalGap() != other.GetThermalGap() )
+        similarity *= 0.9;
+
+    if( GetCustomShapeInZoneOpt() != other.GetCustomShapeInZoneOpt() )
+        similarity *= 0.9;
+
+    if( GetPrimitives().size() != other.GetPrimitives().size() )
+        similarity *= 0.9;
+
+    if( GetAnchorPadShape() != other.GetAnchorPadShape() )
+        similarity *= 0.9;
+
+    if( GetLocalClearance() != other.GetLocalClearance() )
+        similarity *= 0.9;
+
+    if( GetLocalSolderMaskMargin() != other.GetLocalSolderMaskMargin() )
+        similarity *= 0.9;
+
+    if( GetLocalSolderPasteMargin() != other.GetLocalSolderPasteMargin() )
+        similarity *= 0.9;
+
+    if( GetLocalSolderPasteMarginRatio() != other.GetLocalSolderPasteMarginRatio() )
+        similarity *= 0.9;
+
+    if( GetLocalSpokeWidthOverride() != other.GetLocalSpokeWidthOverride() )
+        similarity *= 0.9;
+
+    if( GetLayerSet() != other.GetLayerSet() )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 static struct PAD_DESC
 {
     PAD_DESC()
diff --git a/pcbnew/pad.h b/pcbnew/pad.h
index 11c197414b..3ea37faa08 100644
--- a/pcbnew/pad.h
+++ b/pcbnew/pad.h
@@ -720,6 +720,10 @@ public:
         m_zoneLayerOverrides.at( aLayer ) = aOverride;
     }
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_bitmap.cpp b/pcbnew/pcb_bitmap.cpp
index 8f76879696..82cca11a61 100644
--- a/pcbnew/pcb_bitmap.cpp
+++ b/pcbnew/pcb_bitmap.cpp
@@ -255,6 +255,72 @@ void PCB_BITMAP::ViewGetLayers( int aLayers[], int& aCount ) const
 }
 
 
+bool PCB_BITMAP::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_BITMAP& other = static_cast<const PCB_BITMAP&>( aOther );
+
+    if( m_layer != other.m_layer )
+        return false;
+
+    if( m_pos != other.m_pos )
+        return false;
+
+    if( m_bitmapBase->GetSize() != other.m_bitmapBase->GetSize() )
+        return false;
+
+    if( m_bitmapBase->GetPPI() != other.m_bitmapBase->GetPPI() )
+        return false;
+
+    if( m_bitmapBase->GetScale() != other.m_bitmapBase->GetScale() )
+        return false;
+
+    if( m_bitmapBase->GetImageID() != other.m_bitmapBase->GetImageID() )
+        return false;
+
+    if( m_bitmapBase->GetImageData() != other.m_bitmapBase->GetImageData() )
+        return false;
+
+    return true;
+}
+
+
+double PCB_BITMAP::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_BITMAP& other = static_cast<const PCB_BITMAP&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_layer != other.m_layer )
+        similarity *= 0.9;
+
+    if( m_pos != other.m_pos )
+        similarity *= 0.9;
+
+    if( m_bitmapBase->GetSize() != other.m_bitmapBase->GetSize() )
+        similarity *= 0.9;
+
+    if( m_bitmapBase->GetPPI() != other.m_bitmapBase->GetPPI() )
+        similarity *= 0.9;
+
+    if( m_bitmapBase->GetScale() != other.m_bitmapBase->GetScale() )
+        similarity *= 0.9;
+
+    if( m_bitmapBase->GetImageID() != other.m_bitmapBase->GetImageID() )
+        similarity *= 0.9;
+
+    if( m_bitmapBase->GetImageData() != other.m_bitmapBase->GetImageData() )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 static struct PCB_BITMAP_DESC
 {
     PCB_BITMAP_DESC()
diff --git a/pcbnew/pcb_bitmap.h b/pcbnew/pcb_bitmap.h
index 68236809ac..fe3e15d16b 100644
--- a/pcbnew/pcb_bitmap.h
+++ b/pcbnew/pcb_bitmap.h
@@ -141,6 +141,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override;
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override;
+
 #if defined( DEBUG )
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/pcbnew/pcb_dimension.cpp b/pcbnew/pcb_dimension.cpp
index 47a54a2fd0..81ef56e52f 100644
--- a/pcbnew/pcb_dimension.cpp
+++ b/pcbnew/pcb_dimension.cpp
@@ -62,6 +62,101 @@ PCB_DIMENSION_BASE::PCB_DIMENSION_BASE( BOARD_ITEM* aParent, KICAD_T aType ) :
 }
 
 
+bool PCB_DIMENSION_BASE::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( Type() != aOther.Type() )
+        return false;
+
+    const PCB_DIMENSION_BASE& other = static_cast<const PCB_DIMENSION_BASE&>( aOther );
+
+    if( m_textPosition != other.m_textPosition )
+        return false;
+
+    if( m_keepTextAligned != other.m_keepTextAligned )
+        return false;
+
+    if( m_units != other.m_units )
+        return false;
+
+    if( m_autoUnits != other.m_autoUnits )
+        return false;
+
+    if( m_unitsFormat != other.m_unitsFormat )
+        return false;
+
+    if( m_precision != other.m_precision )
+        return false;
+
+    if( m_suppressZeroes != other.m_suppressZeroes )
+        return false;
+
+    if( m_lineThickness != other.m_lineThickness )
+        return false;
+
+    if( m_arrowLength != other.m_arrowLength )
+        return false;
+
+    if( m_extensionOffset != other.m_extensionOffset )
+        return false;
+
+    if( m_measuredValue != other.m_measuredValue )
+        return false;
+
+    return EDA_TEXT::operator==( other );
+}
+
+
+double PCB_DIMENSION_BASE::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( Type() != aOther.Type() )
+        return 0.0;
+
+    const PCB_DIMENSION_BASE& other = static_cast<const PCB_DIMENSION_BASE&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_textPosition != other.m_textPosition )
+        similarity *= 0.9;
+
+    if( m_keepTextAligned != other.m_keepTextAligned )
+        similarity *= 0.9;
+
+    if( m_units != other.m_units )
+        similarity *= 0.9;
+
+    if( m_autoUnits != other.m_autoUnits )
+        similarity *= 0.9;
+
+    if( m_unitsFormat != other.m_unitsFormat )
+        similarity *= 0.9;
+
+    if( m_precision != other.m_precision )
+        similarity *= 0.9;
+
+    if( m_suppressZeroes != other.m_suppressZeroes )
+        similarity *= 0.9;
+
+    if( m_lineThickness != other.m_lineThickness )
+        similarity *= 0.9;
+
+    if( m_arrowLength != other.m_arrowLength )
+        similarity *= 0.9;
+
+    if( m_extensionOffset != other.m_extensionOffset )
+        similarity *= 0.9;
+
+    if( m_measuredValue != other.m_measuredValue )
+        similarity *= 0.9;
+
+    similarity *= EDA_TEXT::Similarity( other );
+
+    return similarity;
+}
+
+
 void PCB_DIMENSION_BASE::updateText()
 {
     wxString text = m_overrideTextEnabled ? m_valueString : GetValueText();
diff --git a/pcbnew/pcb_dimension.h b/pcbnew/pcb_dimension.h
index 0386f4e7e3..9989a13884 100644
--- a/pcbnew/pcb_dimension.h
+++ b/pcbnew/pcb_dimension.h
@@ -278,6 +278,10 @@ public:
                                   int aError, ERROR_LOC aErrorLoc,
                                   bool aIgnoreLineWidth = false ) const override;
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_field.cpp b/pcbnew/pcb_field.cpp
index 22a90f64bf..ca9e6f60ef 100644
--- a/pcbnew/pcb_field.cpp
+++ b/pcbnew/pcb_field.cpp
@@ -116,7 +116,7 @@ wxString PCB_FIELD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const
                                  KIUI::EllipsizeMenuText( GetText() ),
                                  GetParentFootprint()->GetReference() );
 
-    default: 
+    default:
         break; // avoid unreachable code / missing return statement warnings
     }
 
@@ -158,6 +158,41 @@ EDA_ITEM* PCB_FIELD::Clone() const
 }
 
 
+bool PCB_FIELD::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_FIELD& other = static_cast<const PCB_FIELD&>( aOther );
+
+    return m_id == other.m_id && m_name == other.m_name && EDA_TEXT::operator==( other );
+}
+
+
+double PCB_FIELD::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( m_Uuid == aOther.m_Uuid )
+        return 1.0;
+
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_FIELD& other = static_cast<const PCB_FIELD&>( aOther );
+
+    if( m_id < MANDATORY_FIELDS || other.m_id < MANDATORY_FIELDS )
+    {
+        if( m_id == other.m_id )
+            return 1.0;
+        else
+            return 0.0;
+    }
+
+    if( m_name == other.m_name )
+        return 1.0;
+
+    return EDA_TEXT::Similarity( other );
+}
+
 static struct PCB_FIELD_DESC
 {
     PCB_FIELD_DESC()
diff --git a/pcbnew/pcb_field.h b/pcbnew/pcb_field.h
index c4a54987b6..90e190549d 100644
--- a/pcbnew/pcb_field.h
+++ b/pcbnew/pcb_field.h
@@ -111,6 +111,10 @@ public:
 
     int GetId() const { return m_id; }
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 private:
     int m_id; ///< Field index, @see enum MANDATORY_FIELD_T
 
diff --git a/pcbnew/pcb_group.cpp b/pcbnew/pcb_group.cpp
index 33051c04df..155f35c04b 100644
--- a/pcbnew/pcb_group.cpp
+++ b/pcbnew/pcb_group.cpp
@@ -434,3 +434,50 @@ void PCB_GROUP::RunOnDescendants( const std::function<void( BOARD_ITEM* )>& aFun
         wxFAIL_MSG( wxT( "Error calling function in PCB_GROUP::RunOnDescendants" ) );
     }
 }
+
+bool PCB_GROUP::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_GROUP& other = static_cast<const PCB_GROUP&>( aOther );
+
+    if( m_items.size() != other.m_items.size() )
+        return false;
+
+    // The items in groups are in unordered sets hashed by the pointer value, so we need to
+    // order them by UUID (EDA_ITEM_SET) to compare
+    EDA_ITEM_SET itemSet( m_items.begin(), m_items.end() );
+    EDA_ITEM_SET otherItemSet( other.m_items.begin(), other.m_items.end() );
+
+    for( auto it1 = itemSet.begin(), it2 = otherItemSet.begin(); it1 != itemSet.end(); ++it1, ++it2 )
+    {
+        // Compare UUID instead of the items themselves because we only care if the contents
+        // of the group has changed, not which elements in the group have changed
+        if( ( *it1 )->m_Uuid != ( *it2 )->m_Uuid )
+            return false;
+    }
+
+    return true;
+}
+
+
+double PCB_GROUP::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_GROUP& other = static_cast<const PCB_GROUP&>( aOther );
+
+    double similarity = 0.0;
+
+    for( BOARD_ITEM* item : m_items )
+    {
+        for( BOARD_ITEM* otherItem : other.m_items )
+        {
+            similarity += item->Similarity( *otherItem );
+        }
+    }
+
+    return similarity / m_items.size();
+}
diff --git a/pcbnew/pcb_marker.h b/pcbnew/pcb_marker.h
index b881bd9948..5e1011c9eb 100644
--- a/pcbnew/pcb_marker.h
+++ b/pcbnew/pcb_marker.h
@@ -112,6 +112,16 @@ public:
 
     SEVERITY GetSeverity() const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override
+    {
+        return 0.0;
+    }
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override
+    {
+        return false;
+    }
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp
index 5615d4e52a..a373e9e8cd 100644
--- a/pcbnew/pcb_shape.cpp
+++ b/pcbnew/pcb_shape.cpp
@@ -573,6 +573,68 @@ void PCB_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID a
 }
 
 
+bool PCB_SHAPE::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_SHAPE& other = static_cast<const PCB_SHAPE&>( aOther );
+
+    if( m_layer != other.m_layer )
+        return false;
+
+    if( m_isKnockout != other.m_isKnockout )
+        return false;
+
+    if( m_isLocked != other.m_isLocked )
+        return false;
+
+    if( m_flags != other.m_flags )
+        return false;
+
+    if( m_forceVisible != other.m_forceVisible )
+        return false;
+
+    if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() )
+        return false;
+
+    return EDA_SHAPE::operator==( other );
+}
+
+
+double PCB_SHAPE::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_SHAPE& other = static_cast<const PCB_SHAPE&>( aOther );
+
+    double similarity = 1.0;
+
+    if( GetLayer() != other.GetLayer() )
+        similarity *= 0.9;
+
+    if( m_isKnockout != other.m_isKnockout )
+        similarity *= 0.9;
+
+    if( m_isLocked != other.m_isLocked )
+        similarity *= 0.9;
+
+    if( m_flags != other.m_flags )
+        similarity *= 0.9;
+
+    if( m_forceVisible != other.m_forceVisible )
+        similarity *= 0.9;
+
+    if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() )
+        similarity *= 0.9;
+
+    similarity *= EDA_SHAPE::Similarity( other );
+
+    return similarity;
+}
+
+
 static struct PCB_SHAPE_DESC
 {
     PCB_SHAPE_DESC()
diff --git a/pcbnew/pcb_shape.h b/pcbnew/pcb_shape.h
index 1a5a224831..cfec12a4e5 100644
--- a/pcbnew/pcb_shape.h
+++ b/pcbnew/pcb_shape.h
@@ -160,6 +160,10 @@ public:
     ///< @copydoc VIEW_ITEM::ViewGetLOD
     double ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override;
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_target.cpp b/pcbnew/pcb_target.cpp
index 0386f7c242..ad13e865c3 100644
--- a/pcbnew/pcb_target.cpp
+++ b/pcbnew/pcb_target.cpp
@@ -189,6 +189,45 @@ void PCB_TARGET::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID
 }
 
 
+bool PCB_TARGET::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_TARGET& other = static_cast<const PCB_TARGET&>( aOther );
+
+    return m_shape == other.m_shape && m_size == other.m_size && m_lineWidth == other.m_lineWidth
+           && m_layer == other.m_layer && m_pos == other.m_pos;
+}
+
+
+double PCB_TARGET::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_TARGET& other = static_cast<const PCB_TARGET&>( aOther );
+
+    double similarity = 1.0;
+
+    if( GetShape() != other.GetShape() )
+        similarity *= 0.9;
+
+    if( GetSize() != other.GetSize() )
+        similarity *= 0.9;
+
+    if( GetWidth() != other.GetWidth() )
+        similarity *= 0.9;
+
+    if( GetLayer() != other.GetLayer() )
+        similarity *= 0.9;
+
+    if( GetPosition() != other.GetPosition() )
+        similarity *= 0.9;
+
+    return 1.0;
+}
+
 static struct PCB_TARGET_DESC
 {
     PCB_TARGET_DESC()
diff --git a/pcbnew/pcb_target.h b/pcbnew/pcb_target.h
index 90bf4362b6..ace5b06e79 100644
--- a/pcbnew/pcb_target.h
+++ b/pcbnew/pcb_target.h
@@ -110,6 +110,10 @@ public:
                                   int aError, ERROR_LOC aErrorLoc,
                                   bool ignoreLineWidth = false ) const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override;
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_text.cpp b/pcbnew/pcb_text.cpp
index 27e6d7f429..33e846a15b 100644
--- a/pcbnew/pcb_text.cpp
+++ b/pcbnew/pcb_text.cpp
@@ -527,6 +527,28 @@ void PCB_TEXT::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aL
 }
 
 
+bool PCB_TEXT::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_TEXT& other = static_cast<const PCB_TEXT&>( aOther );
+
+    return EDA_TEXT::operator==( other );
+}
+
+
+double PCB_TEXT::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_TEXT& other = static_cast<const PCB_TEXT&>( aOther );
+
+    return EDA_TEXT::Similarity( other );
+}
+
+
 static struct PCB_TEXT_DESC
 {
     PCB_TEXT_DESC()
diff --git a/pcbnew/pcb_text.h b/pcbnew/pcb_text.h
index bada23a393..dcf647ed10 100644
--- a/pcbnew/pcb_text.h
+++ b/pcbnew/pcb_text.h
@@ -161,6 +161,10 @@ public:
 
     EDA_ITEM* Clone() const override;
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override;
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override;
+
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 #endif
diff --git a/pcbnew/pcb_textbox.cpp b/pcbnew/pcb_textbox.cpp
index fabcbd4789..d09085b52c 100644
--- a/pcbnew/pcb_textbox.cpp
+++ b/pcbnew/pcb_textbox.cpp
@@ -584,6 +584,36 @@ void PCB_TEXTBOX::SetBorderWidth( const int aSize )
 }
 
 
+
+bool PCB_TEXTBOX::operator==( const BOARD_ITEM& aBoardItem ) const
+{
+    if( aBoardItem.Type() != Type() )
+        return false;
+
+    const PCB_TEXTBOX& other = static_cast<const PCB_TEXTBOX&>( aBoardItem );
+
+    return m_borderEnabled == other.m_borderEnabled && EDA_TEXT::operator==( other );
+}
+
+
+double PCB_TEXTBOX::Similarity( const BOARD_ITEM& aBoardItem ) const
+{
+    if( aBoardItem.Type() != Type() )
+        return 0.0;
+
+    const PCB_TEXTBOX& other = static_cast<const PCB_TEXTBOX&>( aBoardItem );
+
+    double similarity = 1.0;
+
+    if( m_borderEnabled != other.m_borderEnabled )
+        similarity *= 0.9;
+
+    similarity *= EDA_TEXT::Similarity( other );
+
+    return similarity;
+}
+
+
 static struct PCB_TEXTBOX_DESC
 {
     PCB_TEXTBOX_DESC()
diff --git a/pcbnew/pcb_textbox.h b/pcbnew/pcb_textbox.h
index ce70348b3f..65424dc7e9 100644
--- a/pcbnew/pcb_textbox.h
+++ b/pcbnew/pcb_textbox.h
@@ -149,6 +149,10 @@ public:
     void SetBorderWidth( const int aSize );
     int  GetBorderWidth() const { return m_stroke.GetWidth(); }
 
+    double Similarity( const BOARD_ITEM& aBoardItem ) const override;
+
+    bool operator==( const BOARD_ITEM& aBoardItem ) const override;
+
 protected:
     bool m_borderEnabled; ///< Controls drawing the border (as defined by the stroke members)
 
diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp
index 69283f0c29..571e53825e 100644
--- a/pcbnew/pcb_track.cpp
+++ b/pcbnew/pcb_track.cpp
@@ -151,6 +151,142 @@ BITMAPS PCB_VIA::GetMenuImage() const
 }
 
 
+bool PCB_TRACK::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_TRACK& other = static_cast<const PCB_TRACK&>( aOther );
+
+    return m_Start == other.m_Start && m_End == other.m_End && m_layer == other.m_layer &&
+           m_Width == other.m_Width;
+}
+
+
+double PCB_TRACK::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_TRACK& other = static_cast<const PCB_TRACK&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_layer != other.m_layer )
+        similarity *= 0.9;
+
+    if( m_Width != other.m_Width )
+        similarity *= 0.9;
+
+    if( m_Start != other.m_Start )
+        similarity *= 0.9;
+
+    if( m_End != other.m_End )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
+bool PCB_ARC::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_ARC& other = static_cast<const PCB_ARC&>( aOther );
+
+    return m_Start == other.m_Start && m_End == other.m_End && m_Mid == other.m_Mid &&
+           m_layer == other.m_layer && m_Width == other.m_Width;
+}
+
+
+double PCB_ARC::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_ARC& other = static_cast<const PCB_ARC&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_layer != other.m_layer )
+        similarity *= 0.9;
+
+    if( m_Width != other.m_Width )
+        similarity *= 0.9;
+
+    if( m_Start != other.m_Start )
+        similarity *= 0.9;
+
+    if( m_End != other.m_End )
+        similarity *= 0.9;
+
+    if( m_Mid != other.m_Mid )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
+bool PCB_VIA::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const PCB_VIA& other = static_cast<const PCB_VIA&>( aOther );
+
+    return m_Start == other.m_Start && m_End == other.m_End && m_layer == other.m_layer &&
+           m_bottomLayer == other.m_bottomLayer && m_Width == other.m_Width &&
+           m_viaType == other.m_viaType && m_drill == other.m_drill &&
+           m_removeUnconnectedLayer == other.m_removeUnconnectedLayer &&
+           m_keepStartEndLayer == other.m_keepStartEndLayer &&
+           m_zoneLayerOverrides == other.m_zoneLayerOverrides;
+}
+
+
+double PCB_VIA::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const PCB_VIA& other = static_cast<const PCB_VIA&>( aOther );
+
+    double similarity = 1.0;
+
+    if( m_layer != other.m_layer )
+        similarity *= 0.9;
+
+    if( m_Width != other.m_Width )
+        similarity *= 0.9;
+
+    if( m_Start != other.m_Start )
+        similarity *= 0.9;
+
+    if( m_End != other.m_End )
+        similarity *= 0.9;
+
+    if( m_bottomLayer != other.m_bottomLayer )
+        similarity *= 0.9;
+
+    if( m_viaType != other.m_viaType )
+        similarity *= 0.9;
+
+    if( m_drill != other.m_drill )
+        similarity *= 0.9;
+
+    if( m_removeUnconnectedLayer != other.m_removeUnconnectedLayer )
+        similarity *= 0.9;
+
+    if( m_keepStartEndLayer != other.m_keepStartEndLayer )
+        similarity *= 0.9;
+
+    if( m_zoneLayerOverrides != other.m_zoneLayerOverrides )
+        similarity *= 0.9;
+
+    return similarity;
+}
+
+
 bool PCB_TRACK::ApproxCollinear( const PCB_TRACK& aTrack )
 {
     SEG a( m_Start, m_End );
diff --git a/pcbnew/pcb_track.h b/pcbnew/pcb_track.h
index 06681433ff..a7491db6e3 100644
--- a/pcbnew/pcb_track.h
+++ b/pcbnew/pcb_track.h
@@ -249,6 +249,10 @@ public:
         return m_CachedScale;
     }
 
+    virtual double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    virtual bool operator==( const BOARD_ITEM& aOther ) const override;
+
     /**
      * Set the cached scale.
      *
@@ -360,6 +364,10 @@ public:
      */
     bool IsDegenerated( int aThreshold = 5 ) const;
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 protected:
     virtual void swapData( BOARD_ITEM* aImage ) override;
 
@@ -579,6 +587,10 @@ public:
         m_zoneLayerOverrides.at( aLayer ) = aOverride;
     }
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 protected:
     void swapData( BOARD_ITEM* aImage ) override;
 
diff --git a/pcbnew/python/scripting/pcbnew_action_plugins.cpp b/pcbnew/python/scripting/pcbnew_action_plugins.cpp
index d7385fdc1b..58ffe009c0 100644
--- a/pcbnew/python/scripting/pcbnew_action_plugins.cpp
+++ b/pcbnew/python/scripting/pcbnew_action_plugins.cpp
@@ -333,7 +333,7 @@ void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
     PICKED_ITEMS_LIST deletedItemsList;
 
     // The list of existing items after running the action script
-    const std::set<BOARD_ITEM*> currItemList = currentPcb->GetItemSet();
+    const auto currItemList = currentPcb->GetItemSet();
 
     // Found deleted items
     for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
@@ -418,7 +418,7 @@ void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
 void PCB_EDIT_FRAME::RebuildAndRefresh()
 {
     // The list of existing items after running the action script
-    const std::set<BOARD_ITEM*> items = GetBoard()->GetItemSet();
+    const BOARD_ITEM_SET items = GetBoard()->GetItemSet();
 
     // Sync selection with items selection state
     SELECTION&          selection = GetCurrentSelection();
diff --git a/pcbnew/teardrop/teardrop_parameters.h b/pcbnew/teardrop/teardrop_parameters.h
index c9cdba3f0c..85bfc29479 100644
--- a/pcbnew/teardrop/teardrop_parameters.h
+++ b/pcbnew/teardrop/teardrop_parameters.h
@@ -92,6 +92,24 @@ public:
 
     bool IsCurved() const { return m_CurveSegCount > 2; }
 
+    bool operator== ( const TEARDROP_PARAMETERS& aOther ) const
+    {
+        return m_Enabled == aOther.m_Enabled &&
+               m_AllowUseTwoTracks == aOther.m_AllowUseTwoTracks &&
+               m_TdMaxLen == aOther.m_TdMaxLen &&
+               m_TdMaxWidth == aOther.m_TdMaxWidth &&
+               m_BestLengthRatio == aOther.m_BestLengthRatio &&
+               m_BestWidthRatio == aOther.m_BestWidthRatio &&
+               m_CurveSegCount == aOther.m_CurveSegCount &&
+               m_WidthtoSizeFilterRatio == aOther.m_WidthtoSizeFilterRatio &&
+               m_TdOnPadsInZones == aOther.m_TdOnPadsInZones;
+    }
+
+    bool operator!= ( const TEARDROP_PARAMETERS& aOther ) const
+    {
+        return !( *this == aOther );
+    }
+
 public:
     bool    m_Enabled;
     /// True to create teardrops using 2 track segments if the first in too small
diff --git a/pcbnew/zone.cpp b/pcbnew/zone.cpp
index 0a42889731..f148d3e889 100644
--- a/pcbnew/zone.cpp
+++ b/pcbnew/zone.cpp
@@ -1393,6 +1393,140 @@ void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_S
 }
 
 
+bool ZONE::operator==( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return false;
+
+    const ZONE& other = static_cast<const ZONE&>( aOther );
+
+    if( GetIsRuleArea() != other.GetIsRuleArea() )
+        return false;
+
+    if( GetLayerSet() != other.GetLayerSet() )
+        return false;
+
+    if( GetNetCode() != other.GetNetCode() )
+        return false;
+
+    if( GetIsRuleArea() )
+    {
+        if( GetDoNotAllowCopperPour() != other.GetDoNotAllowCopperPour() )
+            return false;
+        if( GetDoNotAllowTracks() != other.GetDoNotAllowTracks() )
+            return false;
+        if( GetDoNotAllowVias() != other.GetDoNotAllowVias() )
+            return false;
+        if( GetDoNotAllowFootprints() != other.GetDoNotAllowFootprints() )
+            return false;
+        if( GetDoNotAllowPads() != other.GetDoNotAllowPads() )
+            return false;
+    }
+    else
+    {
+        if( GetAssignedPriority() != other.GetAssignedPriority() )
+            return false;
+
+        if( GetMinThickness() != other.GetMinThickness() )
+            return false;
+
+        if( GetCornerSmoothingType() != other.GetCornerSmoothingType() )
+            return false;
+
+        if( GetCornerRadius() != other.GetCornerRadius() )
+            return false;
+
+        if( GetTeardropParams() != other.GetTeardropParams() )
+            return false;
+    }
+
+    if( GetNumCorners() != other.GetNumCorners() )
+        return false;
+
+    for( int ii = 0; ii < GetNumCorners(); ii++ )
+    {
+        if( GetCornerPosition( ii ) != other.GetCornerPosition( ii ) )
+            return false;
+    }
+
+    return true;
+}
+
+
+double ZONE::Similarity( const BOARD_ITEM& aOther ) const
+{
+    if( aOther.Type() != Type() )
+        return 0.0;
+
+    const ZONE& other = static_cast<const ZONE&>( aOther );
+
+    if( GetIsRuleArea() != other.GetIsRuleArea() )
+        return 0.0;
+
+    double similarity = 1.0;
+
+    if( GetLayerSet() != other.GetLayerSet() )
+        similarity *= 0.9;
+
+    if( GetNetCode() != other.GetNetCode() )
+        similarity *= 0.9;
+
+    if( !GetIsRuleArea() )
+    {
+        if( GetAssignedPriority() != other.GetAssignedPriority() )
+            similarity *= 0.9;
+
+        if( GetMinThickness() != other.GetMinThickness() )
+            similarity *= 0.9;
+
+        if( GetCornerSmoothingType() != other.GetCornerSmoothingType() )
+            similarity *= 0.9;
+
+        if( GetCornerRadius() != other.GetCornerRadius() )
+            similarity *= 0.9;
+
+        if( GetTeardropParams() != other.GetTeardropParams() )
+            similarity *= 0.9;
+    }
+    else
+    {
+        if( GetDoNotAllowCopperPour() != other.GetDoNotAllowCopperPour() )
+            similarity *= 0.9;
+        if( GetDoNotAllowTracks() != other.GetDoNotAllowTracks() )
+            similarity *= 0.9;
+        if( GetDoNotAllowVias() != other.GetDoNotAllowVias() )
+            similarity *= 0.9;
+        if( GetDoNotAllowFootprints() != other.GetDoNotAllowFootprints() )
+            similarity *= 0.9;
+        if( GetDoNotAllowPads() != other.GetDoNotAllowPads() )
+            similarity *= 0.9;
+    }
+
+    std::vector<VECTOR2I> corners;
+    std::vector<VECTOR2I> otherCorners;
+    VECTOR2I lastCorner( 0, 0 );
+
+    for( int ii = 0; ii < GetNumCorners(); ii++ )
+    {
+        corners.push_back( lastCorner - GetCornerPosition( ii ) );
+        lastCorner = GetCornerPosition( ii );
+    }
+
+    lastCorner = VECTOR2I( 0, 0 );
+    for( int ii = 0; ii < other.GetNumCorners(); ii++ )
+    {
+        otherCorners.push_back( lastCorner - other.GetCornerPosition( ii ) );
+        lastCorner = other.GetCornerPosition( ii );
+    }
+
+    size_t longest = alg::longest_common_subset( corners, otherCorners );
+
+    similarity *= std::pow( 0.9, GetNumCorners() + other.GetNumCorners() - 2 * longest );
+
+    return similarity;
+}
+
+
 static struct ZONE_DESC
 {
     ZONE_DESC()
diff --git a/pcbnew/zone.h b/pcbnew/zone.h
index 7d1c157d4c..b8cce01c7e 100644
--- a/pcbnew/zone.h
+++ b/pcbnew/zone.h
@@ -784,6 +784,10 @@ public:
      */
     MD5_HASH GetHashValue( PCB_LAYER_ID aLayer );
 
+    double Similarity( const BOARD_ITEM& aOther ) const override;
+
+    bool operator==( const BOARD_ITEM& aOther ) const override;
+
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
 
diff --git a/resources/bitmaps_png/CMakeLists.txt b/resources/bitmaps_png/CMakeLists.txt
index ec06956bd2..1a41fe81a4 100644
--- a/resources/bitmaps_png/CMakeLists.txt
+++ b/resources/bitmaps_png/CMakeLists.txt
@@ -72,6 +72,13 @@ set( BMAPS_SMALL
     e_48
     e_96
     e_192
+    git_add
+    git_changed_ahead
+    git_conflict
+    git_delete
+    git_good_check
+    git_modified
+    git_out_of_date
     icon_bitmap2component_16
     icon_eeschema_16
     icon_gerbview_16
diff --git a/resources/bitmaps_png/png/git_add_16.png b/resources/bitmaps_png/png/git_add_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..c044cd21ba243155e6d6ba0c76bf21ebda116d6f
GIT binary patch
literal 120
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`_MR?|Ar_~TfBgS%&#XElu|PUu
z=hLUrr*+rg3rIMy`oDrr3s1wZpZ1J@*!mfPVkrj<I9znt_AJy;<n2Af!od0PJNHH%
SyE#Ck7(8A5T-G@yGywpO7AS}S

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_add_dark_16.png b/resources/bitmaps_png/png/git_add_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..3afee86809705a4f7867546dd43546ae5256a8b9
GIT binary patch
literal 116
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`)}AhoAr_~TfBgS%&#XElu|Qhk
zYQ+;>f&B}QH2hbvY2j)3_0yj558Hgkd<~-n2^J+W=6eBCCbYSwF){2r@l5yJ|58n$
OF$|urelF{r5}E*cx+RbR

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_changed_ahead_16.png b/resources/bitmaps_png/png/git_changed_ahead_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3ac0a4594930d5c1bf1ab92419214e5cfae6906
GIT binary patch
literal 412
zcmV;N0b~A&P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004FNkl<ZILoC{
z%}N6?5N@x62cfpzT@<TtB7zq|MDZQGc<>1(Sx`J^@rgu})Jt{sEfj3+-$Bs2&SWPg
z-K`bz@Ufej$v5A8lTw<drT?H1sDY^lVB%2d1TkVH6sw=!F3%;1q&~#r0U~8|816iX
zz#PmZ;LBf#&j8d#9q!g#E(Em|$ySqcRO6FOWOHXMFuQ(-P?}@v9tm?42(`x~eK(FR
z;+a$GJark`3mAX4Inmm-N<la6IH1LZ00U1H^krvbnG>G9je&BeR;_a^@d<{A*PJ=!
zQ0jN?M)@w{1?S)7|6!h>lV1_~(f2k$_=rI5;P;Ju@ChENpcdaW@+6gWy|{X&mI&m@
zN^CZm?4f9)(3iixJY4Bj!9-dKNwRMMSM}57K$v~386$cjTvw(Z5-06LqE(UGE@x$D
zo$2z}Z!)WcZ(Nt*&b_?Cf=nJql1aQBgt@1go$R>;f5kUDsernVC>jL-0000<MNUMn
GLSTYz!Mg?k

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_changed_ahead_dark_16.png b/resources/bitmaps_png/png/git_changed_ahead_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..4efb0b9e5171c4ae0f620208cdc614529b72f851
GIT binary patch
literal 417
zcmV;S0bc%zP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004KNkl<ZILoC{
z%SyvQ6m7SH3qf7`_zV@ANzsiWqWBMPT=)m-T8p0|mCg+K2|j<KZ8FoEwk`zQ#(O3c
zI!<auTpW_z$GPX8n^KylrT?H1*p}^OCiP_~)H4(69Wd_{+iJ9yW)cJ<KBk5~A+_jO
z<lp^J#z#{LZ1b2f41~I>@YYJ6tBR<~q^co@8kb}uJDW_+djFd$9-o@&QlBuc4=hP9
zU&j<-KNMlI+q_qA&QZK=ay;5GwZbSt?fHa2s8<AC*+9tEnce+?0%fz#eYA!cjCBbH
zOvKtVr_6Z#k;zA1bqNuvW%hmG5vY!TBAU{3E&zOnQti_74L-O8FDM{0SACL=TQ$FV
zmn;CrlSZ^|$({-(GOn(F`}0DnZwe;jBrKCV#HoI24n%ZFH6ub&sW%tBAIT)g_Rcl9
zo#!CiP97OJL#9n|jq6(E--)lVAQS75`xh|DUI%!@&bjQF1b@XB5TnFd)IYR700000
LNkvXXu0mjfGDWzA

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_conflict_16.png b/resources/bitmaps_png/png/git_conflict_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..67e8620e384535e4a57372ff48097fa4130f9ddc
GIT binary patch
literal 303
zcmV+~0nq-5P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002?Nkl<ZILl-B
z|NlP&B^ZTsK+UmnznbHJ5C*cZB8gu`5^uz5z&<s{A|!=JVdDGM9gZQ17veNvznVil
zOyPbtr_Bg)$1N~%pe1p*y`bXci)7P6B$q9M8?5T!gVTV$8ctS7Hub?>pz6>MH(15l
z5}y||;0o0pYmr<~2RB&VQ5~lNhg57ukzA04WN<bjY#c;zyTHML1*qr`RAB^?0pU<X
z|AN%uG=SoSH1G!OQFqn@`t>>3uWC-SkrL7rApQ+Z8&5%M@a2U{L})wyZeVC&f*SAx
zDR$~`=LHo<ZJ?qDK>Q12E0V!eVJ-j~pik8>005a$m|OU0dw~D|002ovPDHLkV1gNY
Be8>O*

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_conflict_dark_16.png b/resources/bitmaps_png/png/git_conflict_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..2247a589d85a553a14ba222f6b731fc46d381239
GIT binary patch
literal 312
zcmV-80muG{P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00030Nkl<ZILl-B
z|NlP&B^ZVCF}1$&Q%c=`Fix$z3KIvhVd5Y)I1TueT3ZA+Af@goTs)=j7~Fv5x<cFr
zB-h0w45;0V<bo}515#?^@EPEXFra=Rk^zelUTg5d<Anw*q%i1%iGNP1?}v*g)mq{<
zAgw_Irtou0T`k;z<k~uf0X6D44fvc`DGE0rwKfakg1T(D0ci~)I1TvU(7*yz^apA{
z1d;*aPy_yg)ZjLN(u6ed27F0r&;$DOImoZTbTAv9#8c{~0L6X-@l%i*d<m%%DJlO3
z1{f1GDgS`S4lqh_M`LQOHc-(6ApQli74CxM`l)aiB-QFuH4Ffwxz%DSJAw`X0000<
KMNUMnLSTY4qksVb

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_delete_16.png b/resources/bitmaps_png/png/git_delete_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..a06932470948f14ad20d6c5b0c52fb90658ec38b
GIT binary patch
literal 407
zcmV;I0cie-P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004ANkl<ZILl-B
z|NlP&B^X0sr?Q*aes!mo{c4Wqf%ppqJDuCF?$8X9!)w3+b*E6EhEG8J9|!&fG$;hC
z0Z`5V9!y{I|5|Y?hUWW|7W}_n)(&F-LpB8AY@q26rY-&t0{{N}{eP{r4WW7R0*LtE
zfB&zScl<w~=J@fDimfQz0I;7moc_Puy6-;>fDHk<1g04V9!y;XHgun=V-vyvHOC7e
z!2_Dk|6gp}g)rpbKNQUmXD>(cmeV<813tqf&<(+^84{q#2B3uegDDG9bpu_39426k
zfZ>2_z<KllLkH*qi|PenBEhEl(VP|kUu@cq)exWy8d0JV==V>r_Z~$G?ZwbAaY2b4
zXjpvMujU|vl8``&<A9dyzt_7DV+0sDV1X`qG;b9+ApxT>2y0RX8FEn5`6ISu3^D{{
zGqPr^nF*A_K#Amly5m`B^nM1hL4Jlg8?OPRVgQck!4lnHU%3DP002ovPDHLkV1kTK
Bvts}N

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_delete_dark_16.png b/resources/bitmaps_png/png/git_delete_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..0daf03a425e0596f3f82752b2993e054d1127a74
GIT binary patch
literal 385
zcmV-{0e=38P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003<Nkl<ZILoDz
zQA)%x5Qg^<>cK=0Ae*jU!I!kVdJEALsGzPWKIkqgir{X=2R(*OgqkgzaVEBD(#8dG
zU?8OVzs&rZb^xIBpKNW&tz;!*d>3;n82_Zi^(5=YJM?WKKz8{$3Irznl0vd%EeI&Z
zTAhy}-#cuHzfP_p-#@0cb|JQGqO0K;Ow8wa_{fUY>CMl+){q|{B;uLe*j}~(>RCiF
z6i=^UgCJOm6_=sTMh1$SfdwEjHMjK=m_4NRX}r-BU)<WN$k-adxU~H?Apvn^s0;9%
zr!u{u-W^X&q7SYI)0Sb)Xc+6YKtmD|tIIo>J-!4XSrqsEXYBPwk-pdB;mCU8zE0`I
z2ktNVgM`B&HV3+xytNG@W3du-bzcqAYIu|lB*}tfj1aon6^DN&oG=;^$@p{mypq<`
fb5-_2z+dwlZuSq?y;{Ci00000NkvXXu0mjfdSkEJ

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_good_check_16.png b/resources/bitmaps_png/png/git_good_check_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c109da9be3b9566f0be7d3e8f022f6034636004
GIT binary patch
literal 236
zcmV<I02BX-P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00029Nkl<ZILl-B
z|NlP&C76Nk2KY?(k@lG8vA|)f0}sgtcun<^aG&OW4QRlBAU@(Z*N>N6%^-Zvd5W_z
zMVg870zS=f1AJ%tasee~f)t|%nCCRlTQJQ)_8ISK-hwEx0M<O+V-Hl(4cE!8QrK(;
zvd<&i3^xEI2vl$ZN#Qm3DegvuY)1Ej2(lqS{EryTD1nH@kpDohoyDgaBNjl4K{n#^
mGr^<`b;%+^HV<58LdO6qhM47(o8^@N0000<MNUMnLSTZ^gkkOg

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_good_check_dark_16.png b/resources/bitmaps_png/png/git_good_check_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c109da9be3b9566f0be7d3e8f022f6034636004
GIT binary patch
literal 236
zcmV<I02BX-P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00029Nkl<ZILl-B
z|NlP&C76Nk2KY?(k@lG8vA|)f0}sgtcun<^aG&OW4QRlBAU@(Z*N>N6%^-Zvd5W_z
zMVg870zS=f1AJ%tasee~f)t|%nCCRlTQJQ)_8ISK-hwEx0M<O+V-Hl(4cE!8QrK(;
zvd<&i3^xEI2vl$ZN#Qm3DegvuY)1Ej2(lqS{EryTD1nH@kpDohoyDgaBNjl4K{n#^
mGr^<`b;%+^HV<58LdO6qhM47(o8^@N0000<MNUMnLSTZ^gkkOg

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_modified_16.png b/resources/bitmaps_png/png/git_modified_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..11ccd2af24f5aed66f2e409bf1cc3c9af20af94c
GIT binary patch
literal 348
zcmV-i0i*tjP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003aNkl<ZILob5
zJ8r^I5Tr>?09+ujE>gOV<o61Cgg6T#srZ~GC8=nzMa}OTKml=5g3a#x*qiVSM4=eT
zvUg@@cGsmeO-uj5*^BX0qZ)d07=m1cAig#5*$W2K+Dql4K;+3b5@JN^LImP-QWq_!
zfFUQWc#U003LrL2DB&Wx?Y#2=Nu8PRKiH!8=k_BCcGWjsv$t_nfzE6?Ah~E@Mnu)2
zi+(NuVr6af&c8uUux_hG<bn&tru*pH+K~8R2YOe5rOCD-o3})WJTYfO3<hj{-NNqm
z;EmPwG67l%S+Z-KfOzFjRA)`j))+!i{&7fj9Q=^9Rm!U}Z?pA*I7T3w&Qm54=EQ{^
uCp&*8ibF?2Lqfur+0cwKTiX`}{)!XzYi~r4Qa*qH0000<MNUMnLSTZ1DVO>H

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_modified_dark_16.png b/resources/bitmaps_png/png/git_modified_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..9bf84efd67ac247d4c187a20b4cd525b64f0558e
GIT binary patch
literal 336
zcmV-W0k8gvP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003ONkl<ZILob5
zL2kk@5X^<X0Q#>A@c<Mj>JLyraYEY{rQe{GZ$J(Zn`4WaO&r?+lTx*Zku1kEJF~k9
zlv2T8aCIS{^T&$uH()IoKN9F%h1si|CZk>f8S)7T0#IH=ypj<gdj&N4<Pf$%p|NxB
zPp*iu?Nt6krJ0K3fvGgfA4JHL`djnn(8j4BP-e&G>{i3HNc|3+7b5`DrnTLvd<k;G
zc&P=}R>0aXej1x#>3iGI{}X_nPc5(`oA*SJjDJ`GEQx6%8g)wmq=ji>lr8NTF%q(5
z#>mon#|h4wY{n?!eArziEXTznl}+9{bLrfFqu7&wl}W@p@nP?h{XY}p(2`J-K#(*c
ioL7{&wcRW5Tbuzy8pa0jONuT40000<MNUMnLSTXp*pndu

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_out_of_date_16.png b/resources/bitmaps_png/png/git_out_of_date_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce6a3d61586ead53c023d65fd789d16bf6260426
GIT binary patch
literal 405
zcmV;G0c!q<P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00048Nkl<ZILoC{
z%SyyR5Uh(AK|xS2UVQwCXD>m~lmFl^s0R_;&0|5?{}FG#zCcfcXm%$V5pf@}I_ZQw
zmWX&LW~O_(rn-8NBnke5M!;TSHS%y8`M8gLd<bMOF&ki|mB9MgXF*IPicpcCW0LI6
zB+%k1F(x`g7wdV>^sr$)xSE@mNebEe@|c$oDEaX191C^1%sWaF<?W;i=}#qgq5wzf
zKEO>FV6ov*rX*p_aY4YqqXb3vXBTVSKcyrduIoySi_MgP#+C^z@t$82BA%b+BE9+M
zI!NK+)%LKhXIm!-D=&$0KE6s55R6E&!*wV=lnEZ>f)rJH?qklJVaZ%(c<E!_1lZCR
z?q-?T{q5(`M89F8j6@{qh@6(<UFIi*i7&8R{YY6d{YWw@al7z?vi0SO!kPV*Gm@xe
z+CY(?O<iGACflJwCt{LL$1-*+J?FAp3I2*7S=f!EVKZ*;00000NkvXXu0mjflfby1

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/png/git_out_of_date_dark_16.png b/resources/bitmaps_png/png/git_out_of_date_dark_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce6a3d61586ead53c023d65fd789d16bf6260426
GIT binary patch
literal 405
zcmV;G0c!q<P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00048Nkl<ZILoC{
z%SyyR5Uh(AK|xS2UVQwCXD>m~lmFl^s0R_;&0|5?{}FG#zCcfcXm%$V5pf@}I_ZQw
zmWX&LW~O_(rn-8NBnke5M!;TSHS%y8`M8gLd<bMOF&ki|mB9MgXF*IPicpcCW0LI6
zB+%k1F(x`g7wdV>^sr$)xSE@mNebEe@|c$oDEaX191C^1%sWaF<?W;i=}#qgq5wzf
zKEO>FV6ov*rX*p_aY4YqqXb3vXBTVSKcyrduIoySi_MgP#+C^z@t$82BA%b+BE9+M
zI!NK+)%LKhXIm!-D=&$0KE6s55R6E&!*wV=lnEZ>f)rJH?qklJVaZ%(c<E!_1lZCR
z?q-?T{q5(`M89F8j6@{qh@6(<UFIi*i7&8R{YY6d{YWw@al7z?vi0SO!kPV*Gm@xe
z+CY(?O<iGACflJwCt{LL$1-*+J?FAp3I2*7S=f!EVKZ*;00000NkvXXu0mjflfby1

literal 0
HcmV?d00001

diff --git a/resources/bitmaps_png/sources/dark/git_add.svg b/resources/bitmaps_png/sources/dark/git_add.svg
new file mode 100644
index 0000000000..39311d6516
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_add.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   x="0px"
+   y="0px"
+   width="16"
+   height="16"
+   viewBox="0 0 16 16.000001"
+   enable-background="new 0 0 32 32"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   sodipodi:docname="git_add.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
+     id="defs3080" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1764"
+     inkscape:window-height="1155"
+     id="namedview3078"
+     showgrid="true"
+     inkscape:zoom="34.352194"
+     inkscape:cx="3.5805573"
+     inkscape:cy="11.16377"
+     inkscape:window-x="53"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2"
+     inkscape:showpageshadow="2"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showguides="false"><inkscape:grid
+       type="xygrid"
+       id="grid19154"
+       spacingx="0.50000002"
+       spacingy="0.50000002"
+       empspacing="2" /></sodipodi:namedview><metadata
+     id="metadata3068"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /></cc:Work><cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"><cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
+     id="background"><rect
+       fill="none"
+       width="32"
+       height="32"
+       id="rect3071"
+       x="0"
+       y="0" /></g><rect
+     style="fill:#1a81c4;fill-opacity:1;stroke-width:0.597615"
+     id="rect19652"
+     width="10"
+     height="2"
+     x="3"
+     y="7"
+     ry="0.5" /><rect
+     style="fill:#1a81c4;fill-opacity:1;stroke-width:0.597615"
+     id="rect19652-6"
+     width="10"
+     height="2"
+     x="-13"
+     y="7"
+     ry="0.5"
+     transform="rotate(-90)" /></svg>
diff --git a/resources/bitmaps_png/sources/dark/git_changed_ahead.svg b/resources/bitmaps_png/sources/dark/git_changed_ahead.svg
new file mode 100644
index 0000000000..8553cb0cd0
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_changed_ahead.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_changed_ahead.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="5.1667534"
+     inkscape:cy="7.7937313"
+     inkscape:window-width="3840"
+     inkscape:window-height="2109"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#42b8eb;stroke-width:1.8827;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="7.9999995"
+     cy="7.999999"
+     rx="6.0586491"
+     ry="6.0586476" />
+  <path
+     style="fill:none;stroke:#42b8eb;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 7.9604604,7.9302856 9.5246072,4.7870594"
+     id="path23661"
+     sodipodi:nodetypes="cc"
+     inkscape:transform-center-x="-0.6810266"
+     inkscape:transform-center-y="-1.6355765" />
+  <path
+     style="fill:none;stroke:#42b8eb;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 8.0062565,8.0538589 11.483111,8.0419533"
+     id="path23661-7"
+     sodipodi:nodetypes="cc" />
+</svg>
diff --git a/resources/bitmaps_png/sources/dark/git_conflict.svg b/resources/bitmaps_png/sources/dark/git_conflict.svg
new file mode 100644
index 0000000000..d61e65cb15
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_conflict.svg
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg11847"
+   sodipodi:docname="git_conflict.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs11851" />
+  <sodipodi:namedview
+     id="namedview11849"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="24.953607"
+     inkscape:cx="5.6504857"
+     inkscape:cy="11.641604"
+     inkscape:window-width="3840"
+     inkscape:window-height="2109"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg11847"
+     showguides="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid13430"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+    <sodipodi:guide
+       position="5.2758953,5.1902767"
+       orientation="1,0"
+       id="guide16065"
+       inkscape:locked="false" />
+  </sodipodi:namedview>
+  <path
+     style="stroke-width:0.00802356;fill:#f2647e;fill-opacity:1;stroke:none"
+     d="M 6.1826259,8.9926137 H 4.152665 c -0.085585,0 -0.1310515,-0.034769 -0.1364005,-0.1043063 L 3.458665,1.1438676 c 0,-0.080236 0.048141,-0.1203534 0.1444241,-0.1203534 h 3.1291884 c 0.096283,0 0.1444241,0.040118 0.1444241,0.1203534 L 6.3190265,8.8883074 c 0,0.037443 -0.013373,0.064189 -0.040118,0.080236 -0.026746,0.016047 -0.05884,0.024071 -0.096283,0.02407 z"
+     id="path13809" />
+  <path
+     style="stroke-width:0.00802356;fill:#f2647e;fill-opacity:1;stroke:none"
+     d="M 11.839236,8.9926137 H 9.8092751 c -0.080236,0 -0.1230285,-0.034769 -0.128377,-0.1043063 L 9.1232984,1.1438676 c 0,-0.080236 0.048141,-0.1203534 0.1444241,-0.1203534 h 3.1291885 c 0.09628,0 0.144424,0.040118 0.144424,0.1203534 L 11.98366,8.8883074 c 0,0.037443 -0.01337,0.064189 -0.04012,0.080236 -0.02675,0.016047 -0.06151,0.024071 -0.104306,0.024071 z"
+     id="path13805" />
+  <metadata
+     id="metadata12696">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <rect
+     style="opacity:1;fill:#f2647e;stroke:none;fill-opacity:1"
+     id="rect13863"
+     width="3.5347683"
+     height="3.0081384"
+     x="8.9933453"
+     y="11.999638"
+     ry="1.4109842" />
+  <rect
+     style="fill:#f2647e;fill-opacity:1;stroke:none"
+     id="rect13863-3"
+     width="3.5347683"
+     height="3.0081384"
+     x="3.4931979"
+     y="11.984678"
+     ry="1.4109842" />
+</svg>
diff --git a/resources/bitmaps_png/sources/dark/git_delete.svg b/resources/bitmaps_png/sources/dark/git_delete.svg
new file mode 100644
index 0000000000..fb4daad52d
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_delete.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="16"
+   height="16"
+   version="1.1"
+   viewBox="0 0 16 16"
+   xml:space="preserve"
+   id="svg521"
+   sodipodi:docname="git_delete.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"><defs
+   id="defs525" /><sodipodi:namedview
+   id="namedview523"
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1.0"
+   inkscape:showpageshadow="2"
+   inkscape:pageopacity="0.0"
+   inkscape:pagecheckerboard="0"
+   inkscape:deskcolor="#d1d1d1"
+   showgrid="false"
+   inkscape:zoom="64.1875"
+   inkscape:cx="6.4342746"
+   inkscape:cy="8"
+   inkscape:window-width="3840"
+   inkscape:window-height="2109"
+   inkscape:window-x="0"
+   inkscape:window-y="27"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="svg521" />
+
+
+<circle
+   cx="8"
+   cy="8"
+   r="7"
+   fill="#bf2641"
+   stroke-width=".032034"
+   id="circle511"
+   style="fill:#f2647e;fill-opacity:1" /><g
+   transform="matrix(.043356 0 0 .043356 -5.0836 -10.951)"
+   fill="#fff"
+   id="g517">
+	
+		<rect
+   transform="matrix(.7071 -.7071 .7071 .7071 -222.62 340.69)"
+   x="267.16"
+   y="307.98"
+   width="65.545"
+   height="262.18"
+   id="rect513" />
+	
+		<rect
+   transform="matrix(.7071 .7071 -.7071 .7071 398.39 -83.312)"
+   x="266.99"
+   y="308.15"
+   width="65.544"
+   height="262.18"
+   id="rect515" />
+</g>
+<metadata
+   id="metadata519"><rdf:RDF><cc:Work
+       rdf:about=""><cc:license
+         rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /></cc:Work><cc:License
+       rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"><cc:permits
+         rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+         rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
+         rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
+         rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:permits
+         rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
+         rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata></svg>
diff --git a/resources/bitmaps_png/sources/dark/git_good_check.svg b/resources/bitmaps_png/sources/dark/git_good_check.svg
new file mode 100644
index 0000000000..9f87f19944
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_good_check.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg106"
+   sodipodi:docname="git_good_check.svg"
+   viewBox="0 0 16 16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs110" />
+  <sodipodi:namedview
+     id="namedview108"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="56.051092"
+     inkscape:cx="9.6340675"
+     inkscape:cy="7.1006646"
+     inkscape:window-width="1920"
+     inkscape:window-height="1154"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg106">
+    <inkscape:grid
+       type="xygrid"
+       id="grid1878"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="1"
+       originx="0"
+       originy="0" />
+  </sodipodi:namedview>
+  <g
+     id="g104"
+     transform="matrix(0.3867032,0,0,0.3867032,0.265936,0.2707942)">
+    <title
+       id="title101">Layer 1</title>
+    <path
+       id="svg_1"
+       d="m 3.5,20.25 10,10 23,-23"
+       stroke-width="7"
+       stroke="#008000"
+       fill="none"
+       style="stroke:#489648;stroke-opacity:1" />
+  </g>
+  <metadata
+     id="metadata5977">
+    <rdf:RDF>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+</svg>
diff --git a/resources/bitmaps_png/sources/dark/git_modified.svg b/resources/bitmaps_png/sources/dark/git_modified.svg
new file mode 100644
index 0000000000..3037931200
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_modified.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_changed.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="7.0198084"
+     inkscape:cy="7.7501301"
+     inkscape:window-width="1514"
+     inkscape:window-height="1247"
+     inkscape:window-x="1742"
+     inkscape:window-y="203"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#f2647e;stroke-width:2.64613;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="8"
+     cy="8.000001"
+     rx="5.6769347"
+     ry="5.6769357" />
+</svg>
diff --git a/resources/bitmaps_png/sources/dark/git_out_of_date.svg b/resources/bitmaps_png/sources/dark/git_out_of_date.svg
new file mode 100644
index 0000000000..a7ed67f47e
--- /dev/null
+++ b/resources/bitmaps_png/sources/dark/git_out_of_date.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_out_of_date.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="7.6084258"
+     inkscape:cy="8.1425417"
+     inkscape:window-width="1514"
+     inkscape:window-height="1247"
+     inkscape:window-x="1742"
+     inkscape:window-y="203"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#f29100;stroke-width:1.8827;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="7.9999995"
+     cy="7.999999"
+     rx="6.0586491"
+     ry="6.0586476" />
+  <path
+     style="fill:none;stroke:#f29100;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 7.9683721,8.0692666 5.4975819,5.5749487"
+     id="path23661"
+     sodipodi:nodetypes="cc" />
+  <path
+     style="fill:none;stroke:#f29100;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 8.0062565,8.0538589 11.483111,8.0419533"
+     id="path23661-7"
+     sodipodi:nodetypes="cc" />
+</svg>
diff --git a/resources/bitmaps_png/sources/light/git_add.svg b/resources/bitmaps_png/sources/light/git_add.svg
new file mode 100644
index 0000000000..35f6bd0b68
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_add.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   x="0px"
+   y="0px"
+   width="16"
+   height="16"
+   viewBox="0 0 16 16.000001"
+   enable-background="new 0 0 32 32"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   sodipodi:docname="git_add.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
+     id="defs3080" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1328"
+     inkscape:window-height="932"
+     id="namedview3078"
+     showgrid="true"
+     inkscape:zoom="34.352194"
+     inkscape:cx="8.6166258"
+     inkscape:cy="8.2673031"
+     inkscape:window-x="53"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2"
+     inkscape:showpageshadow="2"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showguides="false"><inkscape:grid
+       type="xygrid"
+       id="grid19154"
+       spacingx="0.50000002"
+       spacingy="0.50000002"
+       empspacing="2" /></sodipodi:namedview><metadata
+     id="metadata3068"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /></cc:Work><cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"><cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
+     id="background"><rect
+       fill="none"
+       width="32"
+       height="32"
+       id="rect3071"
+       x="0"
+       y="0" /></g><rect
+     style="fill:#39b4ea;fill-opacity:1;stroke-width:0.597614"
+     id="rect19652"
+     width="10"
+     height="2"
+     x="3"
+     y="7"
+     ry="0.5" /><rect
+     style="fill:#39b4ea;fill-opacity:1;stroke-width:0.597614"
+     id="rect19652-6"
+     width="10"
+     height="2"
+     x="-13"
+     y="7"
+     ry="0.5"
+     transform="rotate(-90)" /></svg>
diff --git a/resources/bitmaps_png/sources/light/git_changed_ahead.svg b/resources/bitmaps_png/sources/light/git_changed_ahead.svg
new file mode 100644
index 0000000000..a45a4368a3
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_changed_ahead.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_changed_ahead.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="7.3250174"
+     inkscape:cy="7.7719307"
+     inkscape:window-width="3840"
+     inkscape:window-height="2109"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#1a81c4;stroke-width:1.8827;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="7.9999995"
+     cy="7.999999"
+     rx="6.0586491"
+     ry="6.0586476" />
+  <path
+     style="fill:none;stroke:#1a81c4;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 7.9604604,7.9302856 9.5246072,4.7870594"
+     id="path23661"
+     sodipodi:nodetypes="cc"
+     inkscape:transform-center-x="-0.6810266"
+     inkscape:transform-center-y="-1.6355765" />
+  <path
+     style="fill:none;stroke:#1a81c4;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 8.0062565,8.0538589 11.483111,8.0419533"
+     id="path23661-7"
+     sodipodi:nodetypes="cc" />
+</svg>
diff --git a/resources/bitmaps_png/sources/light/git_conflict.svg b/resources/bitmaps_png/sources/light/git_conflict.svg
new file mode 100644
index 0000000000..eb99b1debb
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_conflict.svg
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg11847"
+   sodipodi:docname="git_conflict.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs11851" />
+  <sodipodi:namedview
+     id="namedview11849"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="24.953607"
+     inkscape:cx="9.6378851"
+     inkscape:cy="11.621566"
+     inkscape:window-width="1920"
+     inkscape:window-height="1154"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg11847"
+     showguides="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid13430"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+    <sodipodi:guide
+       position="5.2758953,5.1902767"
+       orientation="1,0"
+       id="guide16065"
+       inkscape:locked="false" />
+  </sodipodi:namedview>
+  <path
+     style="stroke-width:0.00802356;fill:#bf2641;fill-opacity:1;stroke:none"
+     d="M 6.1826259,8.9926137 H 4.152665 c -0.085585,0 -0.1310515,-0.034769 -0.1364005,-0.1043063 L 3.458665,1.1438676 c 0,-0.080236 0.048141,-0.1203534 0.1444241,-0.1203534 h 3.1291884 c 0.096283,0 0.1444241,0.040118 0.1444241,0.1203534 L 6.3190265,8.8883074 c 0,0.037443 -0.013373,0.064189 -0.040118,0.080236 -0.026746,0.016047 -0.05884,0.024071 -0.096283,0.02407 z"
+     id="path13809" />
+  <path
+     style="stroke-width:0.00802356;fill:#bf2641;fill-opacity:1;stroke:none"
+     d="M 11.839236,8.9926137 H 9.8092751 c -0.080236,0 -0.1230285,-0.034769 -0.128377,-0.1043063 L 9.1232984,1.1438676 c 0,-0.080236 0.048141,-0.1203534 0.1444241,-0.1203534 h 3.1291885 c 0.09628,0 0.144424,0.040118 0.144424,0.1203534 L 11.98366,8.8883074 c 0,0.037443 -0.01337,0.064189 -0.04012,0.080236 -0.02675,0.016047 -0.06151,0.024071 -0.104306,0.024071 z"
+     id="path13805" />
+  <metadata
+     id="metadata12696">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <rect
+     style="opacity:1;fill:#bf2641;stroke:none;fill-opacity:1"
+     id="rect13863"
+     width="3.5347683"
+     height="3.0081384"
+     x="8.9933453"
+     y="11.999638"
+     ry="1.4109842" />
+  <rect
+     style="fill:#bf2641;fill-opacity:1;stroke:none"
+     id="rect13863-3"
+     width="3.5347683"
+     height="3.0081384"
+     x="3.4931979"
+     y="11.984678"
+     ry="1.4109842" />
+</svg>
diff --git a/resources/bitmaps_png/sources/light/git_delete.svg b/resources/bitmaps_png/sources/light/git_delete.svg
new file mode 100644
index 0000000000..39d339c4ec
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_delete.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+
+<circle cx="8" cy="8" r="7" fill="#bf2641" stroke-width=".032034"/><g transform="matrix(.043356 0 0 .043356 -5.0836 -10.951)" fill="#fff">
+	
+		<rect transform="matrix(.7071 -.7071 .7071 .7071 -222.62 340.69)" x="267.16" y="307.98" width="65.545" height="262.18"/>
+	
+		<rect transform="matrix(.7071 .7071 -.7071 .7071 398.39 -83.312)" x="266.99" y="308.15" width="65.544" height="262.18"/>
+</g>
+<metadata><rdf:RDF><cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"><cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/><cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/><cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/><cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/><cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/><cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/></cc:License><cc:Work rdf:about=""><cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/></cc:Work></rdf:RDF></metadata></svg>
diff --git a/resources/bitmaps_png/sources/light/git_good_check.svg b/resources/bitmaps_png/sources/light/git_good_check.svg
new file mode 100644
index 0000000000..9f87f19944
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_good_check.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg106"
+   sodipodi:docname="git_good_check.svg"
+   viewBox="0 0 16 16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs110" />
+  <sodipodi:namedview
+     id="namedview108"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="56.051092"
+     inkscape:cx="9.6340675"
+     inkscape:cy="7.1006646"
+     inkscape:window-width="1920"
+     inkscape:window-height="1154"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg106">
+    <inkscape:grid
+       type="xygrid"
+       id="grid1878"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="1"
+       originx="0"
+       originy="0" />
+  </sodipodi:namedview>
+  <g
+     id="g104"
+     transform="matrix(0.3867032,0,0,0.3867032,0.265936,0.2707942)">
+    <title
+       id="title101">Layer 1</title>
+    <path
+       id="svg_1"
+       d="m 3.5,20.25 10,10 23,-23"
+       stroke-width="7"
+       stroke="#008000"
+       fill="none"
+       style="stroke:#489648;stroke-opacity:1" />
+  </g>
+  <metadata
+     id="metadata5977">
+    <rdf:RDF>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+</svg>
diff --git a/resources/bitmaps_png/sources/light/git_modified.svg b/resources/bitmaps_png/sources/light/git_modified.svg
new file mode 100644
index 0000000000..7cd76e34bd
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_modified.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_changed.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="7.0198084"
+     inkscape:cy="7.7501301"
+     inkscape:window-width="1514"
+     inkscape:window-height="1247"
+     inkscape:window-x="1742"
+     inkscape:window-y="203"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#bf2641;stroke-width:2.64613;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="8"
+     cy="8.000001"
+     rx="5.6769347"
+     ry="5.6769357" />
+</svg>
diff --git a/resources/bitmaps_png/sources/light/git_out_of_date.svg b/resources/bitmaps_png/sources/light/git_out_of_date.svg
new file mode 100644
index 0000000000..4240cbfefc
--- /dev/null
+++ b/resources/bitmaps_png/sources/light/git_out_of_date.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 64 64"
+   version="1.1"
+   id="svg7968"
+   sodipodi:docname="git_out_of_date.svg"
+   width="16"
+   height="16"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#">
+  <defs
+     id="defs7972" />
+  <sodipodi:namedview
+     id="namedview7970"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="true"
+     inkscape:zoom="45.870198"
+     inkscape:cx="5.7335702"
+     inkscape:cy="8.1643423"
+     inkscape:window-width="1514"
+     inkscape:window-height="1247"
+     inkscape:window-x="1742"
+     inkscape:window-y="203"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg7968">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8817"
+       spacingx="0.5"
+       spacingy="0.5"
+       empspacing="2" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8819">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <ellipse
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#f29100;stroke-width:1.8827;stroke-dasharray:none;stroke-opacity:1"
+     id="path23659"
+     cx="7.9999995"
+     cy="7.999999"
+     rx="6.0586491"
+     ry="6.0586476" />
+  <path
+     style="fill:none;stroke:#f29100;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 7.9683721,8.0692666 5.4975819,5.5749487"
+     id="path23661"
+     sodipodi:nodetypes="cc" />
+  <path
+     style="fill:none;stroke:#f29100;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+     d="M 8.0062565,8.0538589 11.483111,8.0419533"
+     id="path23661-7"
+     sodipodi:nodetypes="cc" />
+</svg>