From 9f6366d554bf07cf5f9bdf1eaa91811ca5105dca Mon Sep 17 00:00:00 2001
From: Lansana DIOMANDE <ldiomande@takima.fr>
Date: Thu, 5 May 2022 15:40:16 +0200
Subject: [PATCH] feat: automatic code source save

---
 .../deadlock-extension/src/config.prod.ts     |   5 +-
 .../deadlock-extension/src/config.ts          |   1 +
 .../deadlock-extension/src/core/gitMission.ts |  45 ++-
 .../src/recorder/command-recorder.ts          |   4 +-
 .../deadlock-extension/src/recorder/index.ts  |  16 +-
 .../src/recorder/package-lock.json            | 283 ++++++++++++++++++
 .../src/recorder/package.json                 |   1 +
 .../src/recorder/preStop.ts                   |   4 +-
 .../src/recorder/services/automatic-save.ts   |  19 ++
 .../src/recorder/services/file-watcher.ts     |  21 ++
 .../src/recorder/services/http-server.ts      |   4 +-
 .../deadlock-extension/src/recorder/utils.ts  |  42 ++-
 12 files changed, 428 insertions(+), 17 deletions(-)
 create mode 100644 deadlock-plugins/deadlock-extension/src/recorder/services/automatic-save.ts
 create mode 100644 deadlock-plugins/deadlock-extension/src/recorder/services/file-watcher.ts

diff --git a/deadlock-plugins/deadlock-extension/src/config.prod.ts b/deadlock-plugins/deadlock-extension/src/config.prod.ts
index f1e1b88b..285c2bca 100644
--- a/deadlock-plugins/deadlock-extension/src/config.prod.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.prod.ts
@@ -4,5 +4,6 @@ export const KEYCLOAK_TOKEN_CREATE_URL = 'https://auth.deadlock.io/auth/realms/D
 export const KEYCLOAK_USER_INFO_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
 export const REJECT_UNAUTHORIZED = true;
 export const ENABLE_HTTP_SERVER = false;
-export const HTTP_SERVER_PORT = 8751;
-export const HTTP_SERVER_URL = `http://localhost:${HTTP_SERVER_PORT}`;
+export const ENABLE_AUTOMATIC_SAVE = true;
+export const RECORDER_HTTP_SERVER_PORT = 8751;
+export const RECORDER_HTTP_SERVER_URL = `http://localhost:${RECORDER_HTTP_SERVER_PORT}`;
diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts
index 97cd3910..dc9064f3 100644
--- a/deadlock-plugins/deadlock-extension/src/config.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.ts
@@ -6,5 +6,6 @@ export const KEYCLOAK_USER_INFO_URL =
   'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
 export const REJECT_UNAUTHORIZED = false;
 export const ENABLE_HTTP_SERVER = false;
+export const ENABLE_AUTOMATIC_SAVE = true;
 export const HTTP_SERVER_PORT = 8751;
 export const HTTP_SERVER_URL = `http://localhost:${HTTP_SERVER_PORT}`;
diff --git a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
index 673945eb..dbd69e6e 100644
--- a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts
@@ -7,7 +7,12 @@ const util = require('util');
 const exec = util.promisify(require('child_process').exec);
 
 const DEFAULT_REMOTE = 'origin';
-const DEFAULT_BRANCH = 'master';
+
+export enum Branch {
+  MASTER = 'master',
+  DEFAULT = MASTER,
+  LIVE = 'live',
+}
 
 export default class GitMission {
   private git: SimpleGit;
@@ -55,6 +60,7 @@ export default class GitMission {
       const remotePath = this.getRemotePath();
 
       await this.git.init();
+
       await this.git.addRemote(DEFAULT_REMOTE, remotePath);
       await this.git.addConfig('user.email', this.userConfig.getCurrentUserDetails().email, false, 'system');
       await this.git.addConfig(
@@ -88,20 +94,47 @@ export default class GitMission {
     return this.git.fetch(DEFAULT_REMOTE);
   }
 
-  pull() {
-    return this.git.pull(DEFAULT_REMOTE, DEFAULT_BRANCH, { '--rebase': 'true' });
+  pull(branch?: string) {
+    return this.git.pull(DEFAULT_REMOTE, branch ?? Branch.DEFAULT, { '--rebase': 'true' });
   }
 
   addAll() {
     return this.git.add('.');
   }
 
-  commit(message) {
-    return this.git.commit(message);
+  commit(message: string, options?: string[]) {
+    return this.git.commit(message, options ?? []);
   }
 
   push() {
-    return this.git.push(DEFAULT_REMOTE, DEFAULT_BRANCH);
+    return this.git.push(DEFAULT_REMOTE);
+  }
+
+  createLocalBranch(branch: string) {
+    return this.git.checkout(['-b', branch]);
+  }
+
+  createRemoteBranch(branch: string) {
+    return this.git.push(['-u', DEFAULT_REMOTE, branch]);
+  }
+
+  setUpstream(branch: string) {
+    return this.git.branch(['-u', DEFAULT_REMOTE, branch]);
+  }
+
+  createBranch(branch: string) {
+    const createBranchLocally = this.createLocalBranch(branch);
+    const createRemoteBranch = this.createRemoteBranch(branch);
+
+    return Promise.all([createBranchLocally, createRemoteBranch]);
+  }
+
+  checkout(branch: string) {
+    return this.git.checkout(branch);
+  }
+
+  merge(options?: string[]) {
+    return this.git.merge(options ?? []);
   }
 
   async isRemoteRepoExist() {
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts b/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
index 99dbc521..85bdbf9b 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/command-recorder.ts
@@ -1,5 +1,5 @@
 import GitMission from '../core/gitMission';
-import { error, commitAndPushCode, CommitFrom, log } from './utils';
+import { error, commitAndPushCode, CommitFrom, log, updateRemote } from './utils';
 const async = require('async');
 const fs = require('fs');
 
@@ -33,7 +33,7 @@ export default class CommandRecorder {
     if (!this.commandsInProgress.has(pid)) {
       this.commandsInProgress.set(pid, new Command(pid, command));
       try {
-        this.queue.push(async () => await commitAndPushCode(this.gitMission, CommitFrom.Run));
+        this.queue.push(async () => await updateRemote(this.gitMission, CommitFrom.Run));
       } catch (e) {
         console.error('Cannot send user code to git');
         console.error(e);
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/index.ts b/deadlock-plugins/deadlock-extension/src/recorder/index.ts
index 7ed0b6d8..6e10a2b8 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/index.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/index.ts
@@ -1,3 +1,5 @@
+import { Branch } from './../core/gitMission';
+import { ENABLE_AUTOMATIC_SAVE } from './../config';
 import CommandRecorder from './command-recorder';
 import GitMission from '../core/gitMission';
 import UserConfigNode from './userConfigNode';
@@ -6,6 +8,8 @@ import { copyProjectSources, clearFilesExceptGit, log, error, renameTempToUserGi
 import UserConfig from '../core/userConfig';
 import HttpServer from './services/http-server';
 import { ENABLE_HTTP_SERVER } from '../config';
+import FileWatcher from './services/file-watcher';
+import AutomaticSave from './services/automatic-save';
 export default class Recorder {
   async setupProject(userConfig: UserConfig, gitMission?: GitMission) {
     log('Setup user project..');
@@ -29,6 +33,7 @@ export default class Recorder {
   async setupFromRemoteRepo(gitMission: GitMission) {
     log('Check if remote repo exist');
     const isRemoteRepoExist = await gitMission.isRemoteRepoExist();
+
     if (isRemoteRepoExist) {
       // rm all except git directory pull remote code and setup
       log('Cleaning files to pull repo');
@@ -36,7 +41,14 @@ export default class Recorder {
       await clearFilesExceptGit(PROJECT_SRC_PATH);
 
       log('Pulling user repo');
-      await gitMission.pull();
+
+      await gitMission.fetch();
+      await gitMission.checkout(Branch.MASTER);
+      await gitMission.checkout(Branch.LIVE);
+    } else {
+      await gitMission.commit('initial commit', ['--allow-empty']);
+      await gitMission.createRemoteBranch(Branch.DEFAULT);
+      await gitMission.createBranch(Branch.LIVE);
     }
   }
 
@@ -53,6 +65,8 @@ export default class Recorder {
 
       if (ENABLE_HTTP_SERVER) new HttpServer(gitMission);
       await this.setupFromRemoteRepo(gitMission);
+
+      if (ENABLE_AUTOMATIC_SAVE) new AutomaticSave(PROJECT_DEADLOCK_DESKTOP_PATH, gitMission);
     } catch (e) {
       error('Cannot setup user repo.');
       error(e);
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/package-lock.json b/deadlock-plugins/deadlock-extension/src/recorder/package-lock.json
index 2201c649..851d67cb 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/package-lock.json
+++ b/deadlock-plugins/deadlock-extension/src/recorder/package-lock.json
@@ -9,6 +9,7 @@
       "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
+        "chokidar": "^3.5.3",
         "express": "^4.18.0"
       },
       "devDependencies": {
@@ -103,11 +104,31 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/anymatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+      "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/array-flatten": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
     },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/body-parser": {
       "version": "1.20.0",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@@ -131,6 +152,17 @@
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/bytes": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -151,6 +183,32 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -275,6 +333,17 @@
         "node": ">= 0.10.0"
       }
     },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/finalhandler": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
@@ -308,6 +377,19 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
     "node_modules/function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -326,6 +408,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/has": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -387,6 +480,44 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
     "node_modules/media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -451,6 +582,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.12.0",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
@@ -483,6 +622,17 @@
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
     },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -531,6 +681,17 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -623,6 +784,17 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
     "node_modules/toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -754,11 +926,25 @@
         "negotiator": "0.6.3"
       }
     },
+    "anymatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+      "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
     "array-flatten": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
     },
+    "binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
+    },
     "body-parser": {
       "version": "1.20.0",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@@ -778,6 +964,14 @@
         "unpipe": "1.0.0"
       }
     },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
     "bytes": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -792,6 +986,21 @@
         "get-intrinsic": "^1.0.2"
       }
     },
+    "chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "requires": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "fsevents": "~2.3.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      }
+    },
     "content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -891,6 +1100,14 @@
         "vary": "~1.1.2"
       }
     },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
     "finalhandler": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
@@ -915,6 +1132,12 @@
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
       "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
     },
+    "fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "optional": true
+    },
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -930,6 +1153,14 @@
         "has-symbols": "^1.0.1"
       }
     },
+    "glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
     "has": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -973,6 +1204,32 @@
       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
       "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
     },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+    },
+    "is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+    },
     "media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -1016,6 +1273,11 @@
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
       "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
     },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+    },
     "object-inspect": {
       "version": "1.12.0",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
@@ -1039,6 +1301,11 @@
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
     },
+    "picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+    },
     "proxy-addr": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -1072,6 +1339,14 @@
         "unpipe": "1.0.0"
       }
     },
+    "readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
     "safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1140,6 +1415,14 @@
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
     },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
     "toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/package.json b/deadlock-plugins/deadlock-extension/src/recorder/package.json
index 87dede28..bb63dc16 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/package.json
+++ b/deadlock-plugins/deadlock-extension/src/recorder/package.json
@@ -9,6 +9,7 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
+    "chokidar": "^3.5.3",
     "express": "^4.18.0"
   },
   "devDependencies": {
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts b/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
index 081c4bde..d4d84ecc 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/preStop.ts
@@ -2,7 +2,7 @@ import UserConfigNode from './userConfigNode';
 import GitMission from '../core/gitMission';
 import { PROJECT_SRC_PATH, PROJECT_DEADLOCK_DESKTOP_PATH } from '../core/config';
 
-import { log, error, commitAndPushCode, CommitFrom } from './utils';
+import { log, error, updateRemote, CommitFrom } from './utils';
 const util = require('util');
 const exec = util.promisify(require('child_process').exec);
 
@@ -39,7 +39,7 @@ async function containsDiff() {
       log('Save user code..');
       const gitMission = await new GitMission(userConfig).init();
       if (await containsDiff()) {
-        await commitAndPushCode(gitMission, CommitFrom.Auto);
+        await updateRemote(gitMission, CommitFrom.Auto);
       }
     }
   } catch (e) {
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/services/automatic-save.ts b/deadlock-plugins/deadlock-extension/src/recorder/services/automatic-save.ts
new file mode 100644
index 00000000..9be2e04f
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/recorder/services/automatic-save.ts
@@ -0,0 +1,19 @@
+import { CommitFrom, getIgnorePatternFromIgnoreFile } from './../utils';
+import { FSWatcher } from 'chokidar';
+import FileWatcher from './file-watcher';
+import * as fs from 'fs';
+import { updateRemote } from '../utils';
+import GitMission from '../../core/gitMission';
+
+export default class AutomaticSave {
+  constructor(private folderToWatch: string, private gitMission: GitMission) {
+    this.setupAutomaticSave();
+  }
+
+  async setupAutomaticSave() {
+    const ignoreFilePatterns = await getIgnorePatternFromIgnoreFile('/deadlock/.gitignore');
+    new FileWatcher(this.folderToWatch, ['**/*'], ignoreFilePatterns, () => {
+      updateRemote(this.gitMission, CommitFrom.Auto);
+    });
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/services/file-watcher.ts b/deadlock-plugins/deadlock-extension/src/recorder/services/file-watcher.ts
new file mode 100644
index 00000000..3a786802
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/recorder/services/file-watcher.ts
@@ -0,0 +1,21 @@
+const chokidar = require('chokidar');
+
+export default class FileWatcher {
+  constructor(
+    private folderToWatch: string,
+    private watchFilePatterns: string[],
+    private ignoreFilePatterns: string[],
+    private onFilesChangeAction: () => void,
+  ) {
+    this.setupWatcher();
+  }
+
+  setupWatcher() {
+    const watcher = chokidar.watch(this.watchFilePatterns, {
+      ignoreInitial: true,
+      cwd: this.folderToWatch,
+      ignored: this.ignoreFilePatterns,
+    });
+    watcher.on('all', this.onFilesChangeAction);
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts b/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts
index 0c1b1d56..6c3d474c 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts
@@ -1,6 +1,6 @@
 import GitMission from '../../core/gitMission';
 import { Express } from 'express';
-import { commitAndPushCode, CommitFrom, log } from '../utils';
+import { updateRemote, CommitFrom, log } from '../utils';
 import { HTTP_SERVER_PORT } from '../../config';
 const express = require('express');
 
@@ -18,7 +18,7 @@ export default class HttpServer {
   setupSaveCodeEndpoints() {
     this.app.post('/save', async (req, res) => {
       try {
-        await commitAndPushCode(this.gitMission, CommitFrom.HttpServer);
+        await updateRemote(this.gitMission, CommitFrom.HttpServer);
         res.status(200).json({
           message: 'Commit & push done.',
         });
diff --git a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
index 9e58a09f..dcda0382 100644
--- a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
+++ b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts
@@ -1,3 +1,4 @@
+import { Branch } from './../core/gitMission';
 import GitMission from '../core/gitMission';
 import { PROJECT_SRC_PATH, PROJECT_DEADLOCK_DESKTOP_PATH } from '../core/config';
 import { format } from 'date-fns';
@@ -113,9 +114,14 @@ export enum CommitFrom {
   HttpServer = 'HttpServer',
 }
 
-export async function commitAndPushCode(gitMission: GitMission, from: CommitFrom) {
+export async function updateRemote(gitMission: GitMission, from: CommitFrom) {
+  if (from === CommitFrom.Auto) commitAndPushCode(gitMission);
+  if (from === CommitFrom.Run) mergeMaster(gitMission);
+}
+
+async function commitAndPushCode(gitMission: GitMission) {
   try {
-    log('Commit & push');
+    log('Doing Commit & push');
     await clearFilesExceptGit(PROJECT_SRC_PATH);
 
     copyGitUserFiles(PROJECT_DEADLOCK_DESKTOP_PATH, PROJECT_SRC_PATH, gitMission.author);
@@ -127,6 +133,7 @@ export async function commitAndPushCode(gitMission: GitMission, from: CommitFrom
     );
 
     const currentDate = format(new Date(), "HH'H'mm'_'dd/LL/y");
+    await gitMission.checkout(Branch.LIVE);
     await gitMission.addAll();
     await gitMission.commit(currentDate);
     await gitMission.push();
@@ -137,3 +144,34 @@ export async function commitAndPushCode(gitMission: GitMission, from: CommitFrom
     throw new Error(e);
   }
 }
+
+async function mergeMaster(gitMission: GitMission) {
+  try {
+    log('Merging live to master');
+
+    const currentDate = format(new Date(), "HH'H'mm'_'dd/LL/y");
+
+    await gitMission.checkout(Branch.DEFAULT);
+    await gitMission.merge(['--squash', '-X', 'theirs', Branch.LIVE]);
+    await gitMission.commit(currentDate);
+    await gitMission.push();
+    log('Merge to master done');
+    await gitMission.checkout(Branch.LIVE);
+  } catch (e) {
+    error(`[${e.status}] cannot commitAndPush code: ${e.stderr}`);
+    error(e.message);
+    throw new Error(e);
+  }
+}
+
+export function getIgnorePatternFromIgnoreFile(ignoreFile: string): Promise<string[]> {
+  return fs.promises
+    .readFile(ignoreFile, { encoding: 'utf-8' })
+    .then((fileContent: string) => {
+      return fileContent.split(/\s*\r?\n\s*/);
+    })
+    .then((lines: string[]) => {
+      return lines.filter((line) => line.trimLeft().startsWith('#') || !!line.trim()).map((line) => `**/${line}`);
+    })
+    .then((filesPatterns: string[]) => [...filesPatterns, '.git']);
+}
-- 
GitLab