Samstag, 11. August 2012

git submodules

Was sind git submodules?

  • git submodules sind externe Repositorys, die über einen festgelegten Pfad in ein anderes Repository (Projekt- oder Arbeitsverzeichnis) eingebunden werden
  • ermöglicht das Einbinden und damit die gemeinsame Nutzung von Code in verschiedenen Projekten
  • externe Änderungen in den eingebundenen Submodulen können übernommen werden
  • lokale Änderungen in den eingebundenen Submodulen können committed und an die entsprechenden externen Submodule Repositorys gepushed werden

Hinzufügen eines Submoduls


Submodule werden über den folgenden Aufruf in ein anderes Repository bzw. Projektverzeichnis eingebunden:

    git submodule add <repository> <path>

<repository> bezeichnet dabei die URL zum einzubindenden Submodul (origin)
<path> bezeichnet den Pfad/Namen des lokal anzulegenden Submodul-Verzeichnisses. Die Angabe ist optional. Fehlt sie, wird der Verzeichnisname aus der URL übernommen.

    $ cd ~/tmp/developer1/myproject
    $ git submodule add ~/tmp/public/global.git
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer1/myproject/global/.git/

Im lokalen Projektverzeichnis myproject befindet sich jetzt das global-Submodul im Unterverzeichnis global. In diesem Verzeichnis können wie im normalen Projektverzeichnis Änderungen gemacht werden, externe Änderungen im Original-Repository via git fetch und git merge geholt und eingebunden werden und schließlich auch gepushed werden.


Wo sehe ich, welche Submodule verwendet werden?


Mit dem Hinzufügen eines Submoduls wird die Datei .gitmodules im Projektverzeichnis angelegt:

    $ git status
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    # new file: .gitmodules
    # new file: global

.gitmodules ist eine Konfigurationsdatei, in der die URL des externen Submodule Repository und der Pfad des lokalen Unterverzeichnisses eingetragen werden.

    $ vi .gitmodules
    [submodule "global"]
      path = global
      url = /Users/simjos/tmp/public/global.git

Werden mehrere Submodule verwendet, erscheinen hier entsprechend mehrere Einträge. Ebenso wie die .gitignore-Datei ist .gitmodules versions-kontrolliert, d. h. sie wird von git getracked und mit dem Projektverzeichnis gepushed und gepulled. Wird das Projekt an anderer Stelle ausgechecked, werden die entsprechenden Submodule automatisch mit eingebunden.


Wie sieht git Submodule?


Wichtig bei der Verwendung von Submodulen ist das Verständnis, wie git eingebundene Submodule sieht bzw. tracked.

Für den Anwender erscheinen Submodule wie normale Unterverzeichnisse im aktuellen Projektverzeichnis. git erkennt und behandelt diese Verzeichnisse aber als Submodule, deren Inhalte nicht getracked werden. Git tracked stattdessen den commit-Punkt des Submoduls.

Die besondere Behandlung als Submodule kann man anhand des Modus (mode) erkennen, der im git-Index eingetragen wird. Normale Dateien und Verzeichnisse erscheinen hier mit mode 100644, Submodule mit mode 160000.

    $ git commit -a -m 'submodule added'
    Created commit 382c3c5: submodule added
     2 files changed, 4 insertions(+), 0 deletions(-)
     create mode 100644 .gitmodules
     create mode 160000 global
   
Sieht man sich den git-Index noch einmal direkt an, ist das Submodule mit dem Modus 160000 und dem SHA-1-Hash 382c3c5 eingetragen. Dieser commit-Punkt bezieht sich auf das externe Submodule-Repository, er existiert nicht im aktuellen Projektverzeichnis. Für das Projektverzeichnis ist damit immer genau festgelegt, mit welchem commit ein Submodule eingebunden ist (ermöglicht exakte Wiederherstellung einer Umgebung z.B. beim Klonen des Projekts, eine symbolische Referenzierung wie z.B. master ist daher allerdings nicht möglich).

     $ git ls-files --stage
     100644 5a199dd569f1412adbe273a3789969fcfc04b123 0  .gitmodules
     160000 382c3c560f661df4fe9c1b31bb8d291721977949 0  global
    
Vorsicht: Aufgrund dieses besonderen Trackings werden lokal im Submodule vorgenommene Änderungen nur angezeigt, wenn man sich im Submodule-Verzeichnis befindet. Erst wenn die lokalen Änderungen im Submodule committed und gepushed werden, erkennt das Projektverzeichnis, dass sich der HEAD des Submoduls geändert hat. Die geänderte Version des Submoduls kann dann via git add und git commit in das Projektverzeichnis übernommen werden.


Klonen eines Projekts mit Submodulen


    $ cd ~/tmp/developer2/myproject
    $ git clone ~/tmp/public/myproject.git
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer2/myproject/.git/

Wird ein bestehende Projekt mit git clone <repository> ausgecheckt, wird das Projektverzeichnis einschließlich der Submodul-Verzeichnisses angelegt. Diese Submodul-Verzeichnisse enthalten allerdings noch keine Dateien. Um die eigentlichen Dateien zu bekommen, müssen zwei weitere Aufrufe gemacht werden:

1. Schritt: git submodule init initialisiert das bzw. die lokalen Submodule-Verzeichnisse:

    git submodule init
    Submodule 'global' (/Users/simjos/tmp/public/global.git) 
       registered for path 'global'
   
2. Schritt: git submodule update holt bzw. checked die Daten/Dateien des Submodule-Repository zu dem im übergeordneten Projekt definierten commit-Punkts aus (aus diesem Grunde befindet man sich lokal auch nicht auf einem bestimmmten Branch des Submoduls)

    $ git submodule update
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer2/myproject/global/.git/
    Submodule path 'global': checked out 
       '3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e'
   
    $ cd global
    $ git branch
    * (no branch)
      master


Lokale Änderungen in eingebundenen Submodulen


Sollen in den eingebundenen Submodulen Änderungen gemacht werden, sollte zuerst ein bestimmter Branch, i.d.R. master ausgecheckt werden.

    $ git checkout master
    Switched to branch 'master'

Nach erfolgten Änderungen werden diese committed und zum Submodule-Repository gepushed:

    $ git commit -a -m "updated the submodule"
   
    $ git push
    Counting objects: 5, done.
    Writing objects: 100% (3/3), 284 bytes, done.
    Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    To /Users/simjos/tmp/public/global.git
       3e07eb1..a05c3bd  master -> master
      
Anschließend werden die Änderungen ins Projekt-Repository übernommen. Sprich, der commit-Punkt, mit dem das Submodul eingebunden ist wird akutalisiert (hier von 3e07eb1 auf a05c3bd)
      
    $ cd ..
    $ git st
    # On branch master
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    # modified:   global
    #
    no changes added to commit 
       (use "git add" and/or "git commit -a")

    $ git commit -a -m 'upated submodule a'
    [master ea532ad] upated submodule a
    1 files changed, 1 insertions(+), 1 deletions(-)

    $ git show
    commit ea532addc5d24a0ea53dd762708f09f805a28757
    Author: Simone Joswig <simone.joswig@sinnerschrader.com>
    Date:   Sat Aug 11 10:29:34 2012 +0200

       upated submodule global

    diff --git a/global b/global
    index 3e07eb1..a05c3bd 160000
    --- a/global
    +++ b/global
    @@ -1 +1 @@
    -Subproject commit 3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e
    +Subproject commit a05c3bda4a35aec1f3a761d1c674873369ceda80

    $ git push
    Counting objects: 3, done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (2/2), done.
    Writing objects: 100% (2/2), 337 bytes, done.
    Total 2 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (2/2), done.
    To /Users/simjos/tmp/public/myproject.git
      26312b5..ea532ad  master -> master


Externe Änderungen in eingebundenen Submodulen


Wurden von anderer Seite Änderungen in den Submodulen vorgenommen, erscheinen diese bei Aktualisierung des Projekts in dem sie eingebunden sind:

    $ cd ~/tmp/developer1/myproject
    $ git pull
    remote: Counting objects: 3, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 2 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (2/2), done.
    From /Users/simjos/tmp/public/myproject
       26312b5..ea532ad  master     -> origin/master
    Updating 26312b5..ea532ad
    Fast-forward
     global |    2 +-
     1 files changed, 1 insertions(+), 1 deletions(-)

     $ git st
     # On branch master
     # Changed but not updated:
     #   (use "git add <file>..." to update what will be committed)
     #   (use "git checkout -- <file>..." 
             to discard changes in working directory)
     #
     #  modified:   global
     #
     no changes added to commit 
        (use "git add" and/or "git commit -a")

     $ ~/tmp/developer1/myproject(master)$ git show
     commit ea532addc5d24a0ea53dd762708f09f805a28757
     Author: Simone Joswig <simone.joswig@sinnerschrader.com>
     Date:   Sat Aug 11 10:29:34 2012 +0200
    
         upated submodule global
    
     diff --git a/global b/global
     index 3e07eb1..a05c3bd 160000
     --- a/global
     +++ b/global
     @@ -1 +1 @@
     -Subproject commit 3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e
     +Subproject commit a05c3bda4a35aec1f3a761d1c674873369ceda80

Mit git submodule update wird das Submodule jetzt auf den im Projekt definierten neuen commit-Punkt a05c3bd aktualisiert:

    $ git submodule update
    remote: Counting objects: 5, done.
    remote: Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From /Users/simjos/tmp/public/global
       3e07eb1..a05c3bd  master     -> origin/master
    Submodule path 'global': checked out 
       'a05c3bda4a35aec1f3a761d1c674873369ceda80'


 Und zum Schluss noch einmal aufgepasst!


Lokale Änderungen des Submoduls nicht gepushed

Nach lokaler Änderung des Submoduls wurde diese Änderung zwar committed und im übergeordneten Projekt ebenfalls committed und gepushed, jedoch nicht ins externe Submodul-Projekt gepushed. Versuchen andere anschließend das Projekt zu aktualisieren, geht es nicht:
   
    $ git pull
    $ git submodule update
    error: pathspec 
       '261dfac35cb99d380eb966e102c1197139f7fa24' 
       did not match any file(s) known to git.
    Did you forget to 'git add'?
    Unable to checkout  
       '261dfac35cb99d380eb966e102c1197139f7fa24' 
       in submodule path 'global'


git submodule update bei lokalen Änderungen im Submodul

Vorsicht beim Aufruf von git submodule update: lokale Änderungen in eingebundenen Submodulen werden dadurch ohne Warnung überschrieben.


Keine Kommentare:

Kommentar veröffentlichen