|

Patches für node_modules erstellen

Um schnell Änderungen an versionierter Open Source Software vornehmen zu können, nutzen wir Patch-Dateien. Natürlich könntest Du auch einen Fork des Projekts erstellen und diesen regelmäßig warten... und alle zukünftigen Änderungen vom Original-Projekt auf diesen Fork anwenden... doch das ist mit einem sehr großen Aufwand verbunden und oft keine langfristige Option.

Eine Alternative ist, im Original-Projekt einen Pull-Request zu stellen. Manchmal dauert es aber zu lange, bis dort ein Pull-Request mit den gewünschten Änderungen in einem Release landet. Hier ist es dann oft hilfreich, eigene Patches zu erstellen und diese anzuwenden. Oft sind die gewünschten Änderungen auch fallspezifisch und haben daher im Original-Projekt nichts verloren. Dann ist ein Patch-File notwendig.

Wie du mit Git dein erstes Patch-File erstellst

Nachdem du deine Änderungen am Projekt vorgenommen hast, generierst du deine Patch-Datei mit dem Befehl git format-patch.

Um deine Änderung anschließend anzuwenden, führst Du git apply-patch aus. Alternativ kann das zum Beispiel ebenfalls mit patch -p0 < some-changes.patch gemacht werden.

Patch-Datei von GitHub-URLs generieren

Bei GitHub und anderen Plattformen kann ganz einfach über ein .patch oder .diff in der URL bei Commits und Pull-Requests eine entsprechende Datei ausgegeben werden.

Beispiel:
Ein Link zu einem Commit auf GitHub.
https://github.com/magento/magento2/commit/158 ... d6

GitHub Website

Ein Link zum gleichen Commit, aber als Raw Patch-Datei auf GitHub. Achte auf das .patch am Ende der URL.
https://github.com/magento/magento2/commit/158 ... d6.patch

GitHub Raw Patch-Datei

Damit erhält man direkt eine passende .patch-Datei, um die Änderungen ganz einfach lokal einzuspielen.

Der Aufbau einer .patch-Datei ...

So eine .patch-Datei kann folgendermaßen aussehen:

From c7b9e39a070857e62a1e7728bb873f7f084d61d2 Mon Sep 29 00:00:00 2001
From: user <user@domain.com>
Date: Sun, 28 Oct 2018 17:01:06 +0530
Subject: commit message

---
 .../some/path/file.ext | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/some/path/from/git/root/file.ext b/some/path/from/git/root/file.ext
index 3a6574bff894..447d626b339b 100644
--- a/some/path/from/git/root/file.ext
+++ b/some/path/from/git/root/file.ext
@@ -35,9 +35,8 @@ define([

             some code
 
-            if (something) {
-                console.log(test);
-            }

+            /* some comment */
+            newmethod();
 
             some code

Nachfolgend eine Erklärung der Zeilen anhand von Kennzeichnungen:

--- Angabe des Commit-Hash, aus dem der Patch stammt 
--- inklusive eines Datums, wann er erstellt wurde.
From c7b9e39a070857e62a1e7728bb873f7f084d61d2 Mon Sep 29 00:00:00 2001
--- Der Git-User, der den Commit erstellt hat
From: user <user@domain.com>
--- Das Datum vom Commit, wann dieser erstellt wurde.
Date: Sun, 28 Oct 2018 17:01:06 +0530
--- Die Nachricht des Commits.
Subject: commit message
--- Trennung der optionalen Metadaten vom Patch.

---

--- Liste der geänderten Dateien.
 .../some/path/file.ext | 5 ++---
--- Zusammenfassung der Änderungen aufgelöst auf die Anzahl 
--- der Dateien und Art der Änderungen (hinzugefügte / entfernte Zeilen).
 1 file changed, 2 insertions(+), 3 deletions(-)

--- Angabe der betroffenen Dateien. a ist die originale, 
--- b die neue Datei mit den Änderungen.
diff --git a/some/path/from/git/root/file.ext b/some/path/from/git/root/file.ext

--- Infos für git inklusive der Dateirechte für die 
--- Erstellung und Anpassung der Dateien.
index 3a6574bff894..447d626b339b 100644

--- Die alte/geänderte Datei bzw. ihr alter Pfad.
--- a/some/path/from/git/root/file.ext

--- Die neue Datei bzw. ihr neuer Pfad mit den Änderungen.
+++ b/some/path/from/git/root/file.ext

--- @@ gefolgt von der Startzeilennummer in der Original-Datei (-35),
--- gefolgt von einem Komma und der Anzahl der betroffenen Zeilen 
--- in der Quelldatei (9), gefolgt von der Startzeilennummer in der
--- Zieldatei (+35) und der dort betroffenen Zeilenanzahl.
--- Gefolgt von @@ und einer groben Kontextinformation.
@@ -35,9 +35,8 @@ define([

--- Der aktuelle Code vor den gewünschten Änderungen.
             some code
             
--- Die ersten Änderungen. Es werden 3 Zeilen entfernt.
-            if (something) {
-                console.log(test);
-            }

--- Der neue Stand der Datei. Es werden an der Stelle 
--- zwei neue Zeilen eingefügt.
+            /* some comment */
+            newmethod();

--- Gleichbleibender Code nach den Änderungen zur Orientierung.
             some code

Node Modules patchen mit patch-package

Damit manuelle Änderungen an Dateien aus Abhängigkeiten einfach in eine .patch-Datei überführt und später jederzeit angewendet werden können, nutzen wir zum Beispiel für Node.js-Projekte patch-package.

Für Composer-Pakete gibt es composer-patches.

Im Grunde machen beide Konzepte genau das, was das native patch von vielen Betriebssystemen macht. Es werden anhand einer patch-Datei entsprechende Änderungen auf Dateisystem-Ebene durchgeführt.

Für die Verwendung von patch-package muss dieses zunächst als devDependency dem Projekt hinzugefügt werden:

yarn add --dev patch-package postinstall-postinstall

oder mit npm:

npm i --dev patch-package

Damit nach der Installation von Paketen (npm install oder yarn install) lokale Patches angewendet werden, muss patch-package bei den Scripten in der package.json eingerichtet werden

"scripts": {
  "postinstall": "patch-package"
}

Wenn wir zum Beispiel Änderungen am Package lodash vorgenommen haben, führen wir folgenden Befehl aus:

yarn patch-package lodash

patch-package installiert dann nochmal die Abhängigkeit in einem temporären Ordner, vergleicht diesen mit dem der geänderten Abhängigkeit und erstellt daraus einen Diff bzw. eine .patch-Datei im Ordner patches und wendet diese dann darauf an.

Mit dem Ausführen von patch-package bzw., wenn über npm oder yarn Änderungen an node_modules durchgeführt werden (update / upgrade, install / add, remove /uninstall) werden die Patches angewendet.

Es ist dann hilfreich die Patches irgendwo zu sammeln und bei mehreren Projekten wiederzuverwenden. Ich habe hierzu zum Beispiel ein eigenes Projekt auf GitHub.