2281 Stimmen

Wie führt man zwei Git-Repositories zusammen?

Stellen Sie sich das folgende Szenario vor:

Ich habe ein kleines experimentelles Projekt A in einem eigenen Git Repo entwickelt. Jetzt ist es ausgereift, und ich möchte, dass A Teil des größeren Projekts B ist, das sein eigenes großes Repository hat. Ich möchte nun A als Unterverzeichnis von B hinzufügen.

Wie kann ich A mit B verschmelzen, ohne dass die Geschichte auf irgendeiner Seite verloren geht?

17 Stimmen

Wenn Sie nur versuchen, zwei Repositories zu einem einzigen zusammenzufassen, ohne beide Repositories behalten zu müssen, werfen Sie einen Blick auf diese Frage: stackoverflow.com/questions/13040958/

0 Stimmen

Für das Zusammenführen von Git Repo in einem benutzerdefinierten Verzeichnis mit Speicherung aller Comits verwenden Sie stackoverflow.com/a/43340714/1772410

69voto

Smar Punkte 7125

Wenn beide Repositories die gleiche Art von Dateien enthalten (z.B. zwei Rails-Repositories für verschiedene Projekte), können Sie die Daten des zweiten Repositories in Ihr aktuelles Repository holen:

git fetch git://repository.url/repo.git master:branch_name

und fügen es dann in das aktuelle Repository ein:

git merge --allow-unrelated-histories branch_name

Wenn Ihre Git-Version kleiner als 2.9 ist, entfernen Sie --allow-unrelated-histories .

Danach kann es zu Konflikten kommen. Sie können sie zum Beispiel auflösen mit git mergetool . kdiff3 kann ausschließlich mit der Tastatur bedient werden, so dass 5 Konfliktdateien beim Lesen des Codes nur wenige Minuten dauern.

Denken Sie daran, die Verschmelzung zu beenden:

git commit

2 Stimmen

Mir gefällt die Einfachheit dieser Lösung, und sie scheint genau das zu sein, wonach ich suche, aber ist sie nicht im Grunde genommen nur gleichbedeutend mit einer git pull --allow-unrelated-histories ?

1 Stimmen

@Prometheus Irgendwie schon. Ich habe es jetzt nicht getestet, aber wahrscheinlich hätte Pull das Hinzufügen des entfernten Repositorys als echtes Remote-Repository erfordert, bei dem es nur den notwendigen Inhalt in einen Zweig holt und diesen Inhalt zusammenführt.

37voto

Calahad Punkte 1278

Bei der Verwendung von merge ging immer wieder der Verlauf verloren, so dass ich schließlich rebase verwendete, da in meinem Fall die beiden Repositories unterschiedlich genug sind, um nicht bei jedem Commit zusammengeführt zu werden:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

\=> Konflikte lösen, dann weitermachen, so oft wie nötig...

git rebase --continue

Dies führt dazu, dass ein Projekt alle Übertragungen von projA enthält, gefolgt von Übertragungen von projB

28voto

Radon Rosborough Punkte 4180

In meinem Fall hatte ich eine my-plugin main-project Repository, und ich wollte so tun, als ob my-plugin war immer in der Region entwickelt worden. plugins Unterverzeichnis von main-project .

Im Grunde habe ich die Geschichte der my-plugin Repository, so dass es den Anschein hat, dass die gesamte Entwicklung im plugins/my-plugin Unterverzeichnis. Dann fügte ich die Entwicklungsgeschichte von my-plugin in die main-project Geschichte und führte die beiden Bäume zusammen. Da es keine plugins/my-plugin Verzeichnis, das bereits in der main-project Repository war dies ein trivialer, konfliktfreier Zusammenschluss. Das resultierende Repository enthielt die gesamte Historie der beiden ursprünglichen Projekte und hatte zwei Wurzeln.

TL;DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

Lange Fassung

Erstellen Sie zunächst eine Kopie der Datei my-plugin Repository, weil wir die Geschichte dieses Repositorys neu schreiben werden.

Navigieren Sie nun zur Wurzel des Dokuments my-plugin Repository, checken Sie Ihren Hauptzweig aus (wahrscheinlich master ), und führen Sie den folgenden Befehl aus. Natürlich sollten Sie für my-plugin et plugins wie auch immer ihr eigentlich heißt.

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

Und nun eine Erklärung. git filter-branch --tree-filter (...) HEAD läuft die (...) auf jeder Übertragung, die von HEAD . Beachten Sie, dass dies direkt mit den für jede Übertragung gespeicherten Daten arbeitet, so dass wir uns keine Gedanken über Begriffe wie "Arbeitsverzeichnis", "Index", "Staging" usw. machen müssen.

Wenn Sie eine filter-branch Befehl fehlschlägt, werden einige Dateien in der Datei .git Verzeichnis und beim nächsten Mal, wenn Sie versuchen filter-branch wird es sich darüber beschweren, es sei denn, Sie geben die -f Option zu filter-branch .

Was den eigentlichen Befehl anbelangt, so hatte ich nicht viel Glück bei der bash um das zu tun, was ich wollte, also benutze ich stattdessen zsh -c zu machen zsh einen Befehl ausführen. Zuerst setze ich die extended_glob die es ermöglicht, die Option ^(...) Syntax in der mv sowie der Befehl glob_dots die es mir ermöglicht, Punktdateien auszuwählen (z. B. .gitignore ) mit einem Glob ( ^(...) ).

Als nächstes verwende ich die mkdir -p Befehl, um beides zu erstellen plugins et plugins/my-plugin zur gleichen Zeit.

Schließlich verwende ich die zsh "Merkmal "negativer Globus ^(.git|plugins) um alle Dateien im Root-Verzeichnis des Repositorys abzugleichen, außer für .git und die neu erstellte my-plugin Ordner. (Ausgenommen .git ist hier vielleicht nicht notwendig, aber der Versuch, ein Verzeichnis in sich selbst zu verschieben, ist ein Fehler).

In meinem Repository enthielt die erste Übertragung keine Dateien, so dass die mv einen Fehler bei der ersten Übertragung zurück (da nichts zum Verschieben verfügbar war). Daher fügte ich eine || true so dass git filter-branch nicht abbrechen würde.

El --all Option sagt filter-branch die Geschichte neu zu schreiben für todo Zweige im Repository und die zusätzlichen -- ist notwendig, um zu sagen git als Teil der Optionsliste für neu zu schreibende Zweige zu interpretieren, anstatt als Option für filter-branch selbst.

Navigieren Sie nun zu Ihrem main-project Repository und checken Sie den Zweig aus, in den Sie zusammenführen wollen. Fügen Sie Ihre lokale Kopie der my-plugin Repository (mit geänderter Historie) als Remote von main-project mit:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Sie werden nun zwei nicht miteinander verbundene Bäume in Ihrem Commit-Verlauf haben, die Sie gut visualisieren können:

$ git log --color --graph --decorate --all

Um sie zusammenzuführen, verwenden Sie:

$ git merge my-plugin/master --allow-unrelated-histories

Beachten Sie, dass in Git vor Version 2.9.0 die --allow-unrelated-histories gibt es nicht. Wenn Sie eine dieser Versionen verwenden, lassen Sie die Option einfach weg: Die Fehlermeldung, die --allow-unrelated-histories verhindert wurde auch hinzugefügt in 2.9.0.

Sie sollten keine Konflikte beim Zusammenführen haben. Falls doch, bedeutet dies wahrscheinlich, dass entweder die filter-branch Befehl nicht richtig funktioniert hat oder bereits ein plugins/my-plugin Verzeichnis in main-project .

Vergewissern Sie sich, dass Sie eine erklärende Commit-Nachricht für alle zukünftigen Mitwirkenden eingeben, die sich fragen, was für ein Hickhack passiert ist, um ein Repository mit zwei Wurzeln zu erstellen.

Sie können den neuen Commit-Graphen, der zwei Root-Commits haben sollte, mit der obigen Grafik visualisieren git log Befehl. Beachten Sie, dass nur die master Der Zweig wird zusammengeführt . Das bedeutet, dass Sie, wenn Sie wichtige Arbeiten an anderen my-plugin Zweige, die Sie in die main-project Baum, sollten Sie davon absehen, die my-plugin entfernt, bis Sie diese Zusammenführungen durchgeführt haben. Wenn Sie das nicht tun, werden die Commits aus diesen Zweigen immer noch in der main-project Repository, aber einige werden unerreichbar und anfällig für eine eventuelle Garbage Collection sein. (Außerdem müssen Sie auf sie mit SHA verweisen, da das Löschen eines entfernten Objekts dessen Remote-Tracking-Zweige entfernt).

Optional können Sie, nachdem Sie alles zusammengeführt haben, was Sie von my-plugin entfernen, können Sie die my-plugin fernbedienen:

$ git remote remove my-plugin

Sie können nun die Kopie der Datei my-plugin Repository, dessen Geschichte Sie verändert haben. In meinem Fall fügte ich auch einen Hinweis auf die Verwerfung der echten my-plugin Repository, nachdem die Zusammenführung abgeschlossen und veröffentlicht wurde.


Getestet auf Mac OS X El Capitan mit git --version 2.9.0 et zsh --version 5.2 . Ihre Erfahrungen können variieren.

Referenzen:

1 Stimmen

Wo --allow-unrelated-histories kommen?

3 Stimmen

@MarceloFilho Prüfen man git-merge . Standardmäßig verweigert der Befehl git merge das Zusammenführen von Historien, die keinen gemeinsamen Vorgänger haben. Mit dieser Option kann diese Sicherheit außer Kraft gesetzt werden, wenn Historien von zwei Projekten zusammengeführt werden, die unabhängig voneinander begonnen haben. Da dies ein sehr seltener Fall ist, gibt es keine Konfigurationsvariable, die dies standardmäßig aktiviert und wird auch nicht hinzugefügt.

0 Stimmen

Sollte verfügbar sein am git version 2.7.2.windows.1 ?

10voto

Rian Punkte 1183

Ich habe versucht, die gleiche Sache für Tage zu tun, ich bin mit Git 2.7.2. Subtree nicht bewahren die Geschichte.

Sie können diese Methode verwenden, wenn Sie das alte Projekt nicht wieder verwenden wollen.

Ich würde vorschlagen, dass Sie zuerst in die Zweigstelle B gehen und in der Zweigstelle arbeiten.

Hier sind die Schritte ohne Verzweigung:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"

# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"

# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Wenn Sie nun eine der Dateien in Unterverzeichnis A protokollieren, erhalten Sie den vollständigen Verlauf

git log --follow A/<file>

Dies war der Beitrag, der mir dabei geholfen hat:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

10voto

eitch Punkte 198

Ich habe eine Menge Informationen hier auf Stack OverFlow usw. gesammelt und es geschafft, ein Skript zu erstellen, das das Problem für mich löst.

Der Nachteil ist, dass nur der Entwicklungszweig eines jeden Repositorys berücksichtigt wird und in einem separaten Verzeichnis in einem völlig neuen Repository zusammengeführt wird.

Tags und andere Zweige werden ignoriert - das ist vielleicht nicht das, was Sie wollen.

Das Skript verarbeitet sogar Feature-Zweige und Tags und benennt sie im neuen Projekt um, damit Sie wissen, woher sie stammen.

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi

## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"

# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}

# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi

# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi

# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo

# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})

  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done

# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done

# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

Sie können es auch erhalten von http://paste.ubuntu.com/11732805

Erstellen Sie zunächst eine Datei mit der URL zu jedem Repository, z. B.:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

Rufen Sie dann das Skript auf und geben Sie den Namen des Projekts und den Pfad zum Skript an:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

Das Skript selbst enthält eine Menge Kommentare, die erklären sollten, was es tut.

1 Stimmen

Anstatt die Leser auf eine Antwort zu verweisen, posten Sie die Antwort bitte hier (bzw. fügen Sie das, was Sie in diesem Kommentar gesagt haben, in diese Antwort ein).

1 Stimmen

Sicher, ich dachte nur, es wäre besser, mich nicht zu wiederholen... =)

0 Stimmen

Wenn Sie der Meinung sind, dass diese Frage mit der anderen identisch ist, können Sie sie als Duplikat kennzeichnen, indem Sie auf den Link "Kennzeichnen" unter der Frage selbst klicken und die andere Frage angeben. Wenn es sich nicht um eine doppelte Frage handelt, Sie aber der Meinung sind, dass dieselbe Antwort zur Lösung beider Probleme verwendet werden kann, dann posten Sie einfach dieselbe Antwort auf beide Fragen (wie Sie es jetzt getan haben). Vielen Dank für Ihren Beitrag!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X