チーム開発対策（Git編）

# 用語

<details>
<summary>ワークツリー</summary>
自身のファイルを変更する作業場
</details>

<details><summary>ステージ</summary>
コミットさせたいファイルを準備しておく場所
</details>

<details><summary>リポジトリ</summary>
ステージのファイルをコミットしてスナップショットを記録する場所
</details>

##### リモートブランチ
* フェッチすることで、「origin/ブランチ」という名前でローカルブランチに保存される
* つまり直接リモートブランチを参照するのではなく、一度origin/ブランチに保存したものを参照するので、間接的にリモートブランチを参照していることになる
* リモートブランチの内容をローカルに保存しているだけなので、直接編集はできない
* １つのブランチに対して以下の３つの差分に分かれる
    * リモートブランチ（github上の内容）
    * origin/ブランチ（フェッチするまでgithubの最新の情報と差分が発生する）
    * ローカルブランチ
* git pullはリモートブランチの内容をorigin/ブランチに取得して、origin/ブランチでローカルブランチにマージしている
* 以下のコマンドでリモート/ブランチも含めてすべてのブランチ一覧を表示できる。
    ```
    git branch -a
    ↓
    * main
      remotes/origin/main
      remotes/origin/develop
    ```
    


</details>

# 初期設定
* インストール後に自身のユーザー名、メールアドレスを登録する
* （編集途中）

# コミットまでの流れ
1. ワークツリーでファイルの変更を行う
1. コミット対象のファイルをステージに追加
1. ステージのファイルをコミットしてリポジトリにスナップショットを記録する

# 各処理の挙動
※処理ごとにgitではGitオブジェクトが作成される
* blob:ファイルの圧縮データ
* tree:blobとファイル名を紐づけるファイル
* commit:コミット情報


## git add
* 変更ファイルをステージに追加する
* ステージに追加されるときに、圧縮ファイル（blobオブジェクト）が作成され、実際のファイル名と紐づけるためのインデックスの様なものが作成される。(この時点ではtreeファイルは作成されない）
* インデックスはgit addするたびに上書きされる

## git commit
* コミットした時点の、インデックス情報をtreeオブジェクトとしてリモートリポジトリに保存する。（この段階でインデックス情報が確定されてtreeが作成される）
* treeオブジェクトはコミット毎に１つではなく、ディレクトリごとに作成される
* つまり、２つのディレクトリの中身に変更があれば、treeオブジェクトは２つ作成される
* treeオブジェクトには、blobオブジェクトとファイル名の紐づけ、子ディレクトリに該当するtreeオブジェクトディレクトリ名が含まれる
    ```
    <treeオブジェクトの中身>
    オブジェクトタイプ | オブジェクト名（ハッシュ） | 実際の名前
    blob | ハッシュ文字 | ファイル名
    tree | ハッシュ文字 | ディレクトリ名
    ```
* commitオブジェクトを作成して以下の情報を保存している
    * プロジェクトの最上位のtreeオブジェクト
    * 前回のcommitオブジェクトを保存している
    * コミット者情報（名前、メールアドレス）
    * コミットメッセージ

## 最初からリポジトリを作成する場合
* gitで監視させたいディレクトリに移動する
* git initを実行
    ```
    ~$ git init
    ```
    * 挙動：ディレクトリに「.git」が作成される
* git addなどで、ワークツリーの変更内容をインデックスに登録、git commit でリポジトリへ登録する
* github側でアクセストークンを発行する
    * settingsを選択
    * Developer Settingsを選択
    * Personal access tokenをクリック
    * generate new token
    * token 発効後にコードをコピーする
* github側でリモートリポジトリを作成する
    * マイページのリポジトリをクリック
    * newをクリックしてリポジトリを作成する
        * 名前の入力
        * descriptionは特に入力不要
        * publicかprivateのどちらかを選択（誰に見られてもいいならpublic）
        * Initialize this repository with:は特にチェック不要
        * createする
    * 「…or push an existing repository from the command line」という部分に以下の様なリモートリポジトリの登録方法が書かれているので、実行をしていきます
* githubのURLをoriginに設定する
    ```
    git remote add origin <登録したいgithubのリポジトリURL>
    ```
    * 上記コマンドの意味：originというキーワードにgithubのURLを登録する
    * 以降はurlをいちいち入力しなくても、originを使ってURLを省略できるので、楽にアップや取得できる
* 初回のプッシュを実行する
    ```
    git push -u origin main
    ```
    * -uオプションを付けることで、以降はgit pushだけのコマンドを実行した場合、origin mainにプッシュしたことになります
    * 初回はgithubのユーザー名を聞かれるのでユーザー名を入力してEnter
    * パスワードを聞かれたら、上記で作成したパーソナルトークンを張り付けてEnter
    * 初回（githubアカウントにアクセスしたことがないPCでアクセス）にもかかわらず、ユーザー名やパスワードの入力を求められなかった場合はPC内部でパスワードなどが保存されてしまっていることがあるので注意→マックだったら、キーなんちゃらというアプリに保管されているらしいので削除しておく

## 他の人が作成したプロジェクトからリポジトリを作る場合
* gitリポジトリのコピーを自身のローカルに作成する
    * 以下のリポジトリ名には、githubのクローン用のhttpsをコピーしてくる
    ```
    ~$ git clone <リポジトリ名>
    ```
    * 上記コマンドにより、github上のファイルと.gitがカレントディレクトリにダウンロードされます。
## 基本的なサイクル
### git add <ステージに移動させるファイル>
> ワークツリーで変更を加えた場合や、新規作成したファイルをステージに登録する場合に使用する
> ワークツリーで削除したファイルを対象にした場合も、ステージに削除したことが記録されるが、ワークツリーからファイルを削除する場合は、以下のgit rmでファイルを削除することをお勧めする

### ワークツリーを変更前に戻す
```
git checkout -- <ファイル名> 
git checkout -- <ディレクトリ名>
```
* どちらもワークツリーで行った変更を元に戻す
* 実行することで、ステージの内容でワークツリーのファイルを上書き（元に戻す）する
    * すでにgit addしている場合は意味がない
### ステージに追加した変更を戻す（git addを取り消し）
```
# ファイル名やディレクトリを指定する
git reset HEAD <ファイル名>
git reset HEAD <ディレクトリ名>

# コミット前のすべてのgit addした内容を取り消す
git reset HEAD .（ピリオド）
```
* git add を取り消すだけなので、ワークツリーの変更内容が元に戻るわけではない
* ワークツリーの変更内容も戻したい場合
    1. 上記コマンドでステージへの変更を取り消す
    1. git checkoutコマンドを実行して、ステージの内容でワークツリーを上書きする
    * git add している時点ではステージとワークツリーのファイルは同一ファイルである
    * git resetで元に戻さないで、git checkoutを行っても意味がない
* 正確には直前にコミットしたリポジトリの内容でステージを上書きしなおしている
* HEADの意味は、最新のコミットを指しているので、最新のコミット内容で上書きするという意味になる

### git rm <削除したいファイル>
> ワークツリーで直接ファイルを削除してからステージに反映させる場合はgit addで反映させるが、ワークツリーの削除と削除の記録をステージに同時に記録する場合は、git rmでまとめて実行可能
> 注意点：
> * 削除が必要な場合は、削除するときに上記コマンドを実行する。削除してから上記コマンドをすることも可能であるが、その場合はgit add と同じ挙動となる（ワークツリーのファイルは既に削除されているため、ワークツリーからファイルを削除するという挙動がなくなる）
> * ワークツリーのファイルが削除されて、削除情報がステージに登録されるので、コミット後にリポジトリからもファイルが削除される
* ディレクトリを削除したい場合
    > git rm -r <削除したいディレクトリ>
* リポジトリ上だけファイルを削除したことにして、ワークツリーには残しておきたい場合
    > git rm --cached <ファイル名>
    > リポジトリ上では管理が不要になったが、ローカルのワークツリーでデバッグするときに必要だから残しておきたい場合などに使う（のかな？）
* git rmで削除したファイルを復活させたい場合
    > git reset HEAD <対象ファイル>

### リモートからローカルに受け取る（フェッチ）
```
git fetch <リモート名> [<ブランチ名>]
```
* リモートリポジトリからデータを取得して、ローカル側のリモートデータ専用の保管場所に配置する
* この段階では、ワークツリーなどのファイルは変更されず、あくまでもリモートの情報を取得しているだけである
* 受け取ったリモートの情報をワークツリーにも反映させたい場合は次のマージを行う
* remotes/リモート名/ブランチ名　の場所にリモートの情報が保管される
* git fetch originと入力した場合、remotes/origin/mainという場所に保管されている

### マージする
```
git merge <ブランチ名>
git merge リモート名/ブランチ名
git merge origin/main
```
* 「merge リモート名/ブランチ名　」コマンドを実行することで、fetchしてきたリモートの情報を作業中（チェックアウト中）のブランチにマージさせることができます
* 引数にブランチ名だけを記載した場合は、自身のローカルリポジトリの内容をHEADにマージするという意味になるので、ローカルリポジトリを最新の内容に更新できていない場合は注意が必要
    * つまり、フェッチした内容をマージするためには、origin/ブランチ名を記載する必要がある
    * git fetch origin mainとした後に、mainブランチがHEADの状態で、git merge mainとした場合は、マージ先とマージ元が全く同じため意味がない
* ３種類のマージ方法
    1. Fast Foward （早送り）
        * 発生条件
            * マージ時
                * 自身のブランチには変更なし
                * 取り込んだブランチにのみ変更あり
        * 結果
            * 他人のコミットがマージされるが、実質的には他人のコミットと全く同じ内容になる
            * ブランチのポインタが枝分かれせずに、前に進むだけとなる
    1. Auto Merge (基本的なマージ）
        * 発生条件
            * マージ時
                * 自身のブランチに変更がある
                * 取り込んだブランチにも変更がある
            * 結果
                * マージにより新しいコミットが作成されブランチが枝分かれする
                * マージコミットファイル（親を２つもつ）が作成される
    1. confrict （競合発生）
        * 同じファイルで、同じ行に対して複数の変更がある場合に発生
        * 「Automatic merge failed;」といったマージが失敗したというメッセージが表示される
        * コンフリクトの解決モードに移行する
        * git statusを実行することで「both modified: 競合ファイル名」というメッセージが表示される
        * VScodeなどを使用している場合は、競合しているファイルがマーキングされる
        * コンフリクト解決後にファイルをgit addして、git commitを行うことで、コンフリクト解決モードが終了する
#### プッシュしたブランチをmainブランチにマージする
##### マージ結果を見せてくれと言われることがある
1. プルリクエスト
    * プッシュしたブランチをmainブランチに対してプルリクエストを行う
1. ローカルでマージする
    * ブランチをプッシュした後に、mainブランチにチェックアウトして、git pull origin マージ元ブランチ名を実行することで、ローカル内でmainブランチへのマージを試すことができる。

### プルをする（フェッチからマージまでを一度に行う）
```
git pull <リモート名> <ブランチ名>
git pull origin main
// 省略可能
git pull

// 以下の処理を一括して行っている
git fetch origin main
git merge origin/main
```

#### fetchとpullの使い分けについて
* 基本的にはfetchを使う法を推奨
* pullは挙動に注意すべき
    * pullを使った場合、引数に指定したリモートブランチをフェッチして、そのリモートブランチの内容で現在チェックアウトしているブランチにマージを行う
    * つまり、コマンド実行時に取得するブランチと現在チェックアウトしているブランチの２点を意識する必要がある
    * コマンドは基本的には１コマンドに対して１挙動なので、gitの内容を理解するためには、git fetchとgit mergeをgit pullで同時に実行しない方が良い
    * git fetchでまずはリモートから最新のコミット情報を取得する。その後にgit mergeで現在のブランチにマージするという２ステップの方が安心して実行できる

### git commit
* git commit (オプション無しの場合）
>エディターが開きコミットメッセージを入力する。保存して閉じることでcommitが実行される）
* git push前の最後のcommit時はgit pull（もしくはgit fetch→merge)を実施してリモートと競合していないかを確認・修正する必要がある

* git commit -m "<メッセージ>"
* git commit -v

### コミットのやり直し
```
git commit --amend
```
* 直前のgit commitの内容を上書きする
* git commit --amend実行時点のステージの内容をcommitに上書きする
    * 同一ファイルに変更があれば、最新のファイル内容がコミット内容として上書きされる
    * 新しく変更したファイルがある場合は、コミット内容に追加される
* 【禁止事項】git pushで既にリモートリポジトリにアップしているコミットに関しては実行禁止（別のメンバーがそのコミットをすでに取り込んでいた場合、不整合が発生してpushできなくなる）
* git push 前のcommitにのみ実行すること
* 例）
    * ファイルAの内容を変更してgit add →　git commit後にファイルAの内容を追加で変更が必要となった。
        1. git add で追加の変更をステージにアップする
        1. git commit --amendを実行する
        1. コミットメッセージの変更も必要であれば修正が可能
        1. ファイルAの最新の変更内容がcommitとして上書きされる


### git status
> コミット前に変更内容を確認する
> やっていることとしては、
> 1. ワークツリーとステージの比較→変更されているがステージにaddされていないファイルの表示
> 1. ステージとリポジトリの比較→ステージにあるリポジトリにコミットしていないファイルの表示
### git diff
> 変更差分を表示する
* add前のファイルの差分は
    > git diff
    > もしくは
    > git diff 対象のファイル名
    > 注意点：
    > * 比較されるのは、ワークツリーとインデックスである
    > * ワークツリーとリポジトリが比較されるわけではない
    > * git addした場合は、ワークツリーとインデックスの間に差分はなくなるので、ここでは差分が表示されなくなる
* add後のファイルの差分は
    > git diff --staged
    > 注意点：
    > * 比較されるのは、インデックスとリポジトリである
    > * 変更したファイルをaddしないとリポジトリと比較できない
    > * コミット後は、インデックスとリポジトリの間に差分はなくなるので、ここでは差分が表示されなくなる
### git log
> 変更履歴を確認する
* git log oneline
    > 一行単位でコミットを履歴を簡易的に表示する
* git log -p <対象ファイル>
    > 指定したファイルの変更差分を確認できる。
* git log -n <表示させたいコミット数>
    > 指定した数だけのコミットを表示する
### git mv <旧ファイル> <新ファイル>
> ファイルの移動を記録する(ディレクトリの移動をせずファイル名を変更する場合も同じ）
> 以下の作業をまとめて実行したことになります。
> 1. mvコマンドや手で直接ファイル名の変更やディレクトリの移動を行う（ワークツリー内）
> 1. git rmもしくはgit addで旧ファイルを削除したことを記録（ワークツリー→インデックス）
> 1. git addで新規のファイルを登録（ワークツリー→インデックス）
> ※git statusではgit mvでファイル名を変更した場合と、git 上記３つの手順でファイル名を変更した場合の表示内容が異なります。しかし、gitはファイルの内容の一致度を確認して、類似内容と判断した場合、リネーム処理を行ったと自動で判定してくれます。

### git push <リモート名> <ブランチ名>
```
git push origin main
```
* originは上記の初回設定で、githubのURLを紐づけているので、githubのリモートリポジトリのmainブランチに対して、プッシュすることになります

## よりGitを使いこなすために
### エイリアスの設定

~$ git config --global alias.<エイリアスコマンド> <gitコマンド>
```
例）
~$ git config --global alias.ci commit
~$ git config --global alias.st status
以降、git ciやgit stで実行できる。
```
＜注意点＞
* --globalをオプションに付けた場合、PC全体の設定に反映されるので、全てのプロジェクトで反映される
* --globalオプションを付けずに実行した場合、対象のプロジェクトだけに反映される

### gitignoreの記載
.gitignoreファイルに記載する
* 指定したファイルを除外する場合
    * /を書かずに、ファイル名をそのまま記載する
        ```
        sample.html
        ```
        * この場合、すべてのフォルダにある同名のファイルが対象となる

  
    * ディレクトリ以下のファイルをすべてを除外する場合
        ```
        dir/
        ``` 
        * この場合、同名のディレクトリをすべて除外する

    * ルートディレクトリを指定する場合
        ```
        /sample.html
        /dir/
        ```
        * この場合、gitignoreがあるフォルダをカレントディレクトリとして、相対的なパスに位置するファイルもしくはディレクトリを除外対象とする

* .gitignoreファイルで除外後はUntracked filesに表示されなくなる

### リモートリポジトリを複数管理する
* リモートリポジトリの新規追加
```
git remote add <リモート名> <リモートURL>
```
* 同記事内でoriginというリモート名に対してリモートリポジトリ（github)のURLを登録する方法を紹介しており、それと同じ操作である
* origin以外にも別のリモートリポジトリ名を定義してURLと紐づけることができる

### リモート情報を確認
```
git remote show <リモート名>
git remote show origin
```
* fetchとpushのURL
* git pullの挙動
* git pushの挙動
* リモートブランチの情報

### リモートの変更・削除
```
// 変更
git remote rename <旧リモート名> <新リモート名>
// 削除
git remote rm <リモート名>
```
* あくまでもリモート名というのはURLの紐づけを行っている変数の様なものなので、リモートリポジトリなどに影響を及ぼすものではない
* リモート名とは、git push origin mainコマンドでいうところの、「origin」のことである

### ブランチの変更・削除
```
// 名前の変更
git branch -m <ブランチ名>
// 削除
git branch -d <ブランチ名>
// 強制削除
git branch -D <ブランチ名>
```
* 名前の変更は、現在チェックアウトしているブランチが対象となる
* -dオプションの場合はブランチを削除するが、mainブランチにマージできていない（最新のコミットがマージできていない）場合は警告文が表示される
* -Dオプションを使うことで、上記警告文を無視して削除できる

### ブランチを分けた開発
* main

### コンフリクト対策
1. 同じファイルを変更しない
1. pullやmergeする前に変更したファイルは全てコミットするか、stashしておく
1. git pullするときは常に現在のブランチを意識する