Git/GitHub演習

Table of Contents

1. Git/GitHub演習の概要

1.1. Git/GitHub演習について

1.1.1. 演習の概要

  1. この演習について
    • この授業ではGitの初心者が,基礎的なGitコマンドの利用方法から, GitHub Flowに基づく協同開発の方法までを学ぶ
  2. 事前準備
    • 事前に gitコマンドが利用できる環境を用意しておくこと
    • CUI端末でのshellによる基本的な操作を知っていると スムーズに演習ができる
  3. 授業の構成
    • 個人演習では,テキストの指示に従い, Git/GitHubを利用するにあたり必要となる知識を学ぶ
    • チーム演習では,GitHubを活用した協同開発の方法を深く学ぼう
  4. 授業の進め方
    1. 演習の解説
      • 講師が授業の進め方を説明する
    2. Git/GitHubを学ぶ個人演習
      • 個人演習を通してGit/GitHubの使い方を学ぶ
    3. チーム演習
      • チームでの開発演習を実施する

2. 個人演習編

2.1. ローカルリポジトリでGitを使う

2.1.1. Gitの操作方法と初期設定

  1. Gitチートシート(カンニング表)
  2. Gitコマンドの実行確認
    • 端末を操作してGitコマンドを起動してみよう.
    • 次のとおり操作することでGitのバージョン番号が確認できる.
    git --version
    
  3. 名前とメールアドレスの登録
    • (まだなら)gitに名前とメールアドレスを登録しておく
    • 次のコマンドの$NAMEと$EMAILを各自の名前とメールアドレスに置き換えて実行せよ
      • 名前はローマ字で設定すること
    git config --global user.name $NAME
    git config --global user.email $EMAIL
    
  4. 設定の確認方法
    • ここまでの設定を確認する
    git config -l
    

2.1.2. Gitのリポジトリ

  1. プロジェクト用のディレクトリ
    • リポジトリとはプロジェクトでソースコードなどを 配置するディレクトリ
      • このディレクトリを「ワーキングディレクトリ (ないしはワーキングツリー)」とも言う
    • Gitによりバージョン管理ができる
      • ファイルに対する編集作業の内容が追跡され,記録される
    • 将来的にGitHubと連携させることで共同作業ができるようになる
  2. Gitリポジトリを利用するには
    • リポジトリを利用する方法には主に2種類ある
      1. git initコマンドで初期化する方法
      2. git cloneコマンドでGitHubから入手する方法
    • ここでは,まず1.について解説する
  3. Gitリポジトリの初期化方法
    • Gitリポジトリ(my_project)を作成する
      • 1〜2行目: ディレクトリを作成して移動
      • 3行目: Gitリポジトリとして初期化
    mkdir ~/my_project
    cd ~/my_project
    git init
    
    • 以降の作業は作成したmy_projectディレクトリで行うこと
      • 現在のディレクトリは「pwd」コマンドで確認できる
  4. リポジトリの状態を確認する方法
    • 現在のリポジトリの状態を確認するコマンドは次のとおり
    git status
    
    • このコマンドは頻繁に使用する
    • 何かうまく行かないことがあったら,このコマンドで状態を確認する癖を つけるとよい
      • 表示される内容の意味は徐々に覚えていけば良い

2.1.3. コミットの作成方法

  1. コミットについて(1)
    • Gitの用語における「コミット」とは,「ひとかたまりの作業」をいう
      • 新しい機能を追加した,バグを直した,ドキュメントの内容を更新した,など
    • Gitは作業の履歴を,コミットを単位として管理する
      • コミットは次々にリポジトリに追加されていく
      • これにより古いバージョンに戻る, 過去の変更内容を確認する,などの操作ができる
  2. コミットについて(2)
    • コミットには,作業の内容を説明するメッセージをつける
    • コミットには自動的にIDが振られることも覚えておくと良い
  3. READMEファイルの作成
    • my_projectリポジトリにREADMEファイルを作成してみよう
    echo "My README file." > README
    
    • プロジェクトには 必ずREADMEファイルを用意 しておくこと
  4. リポジトリの状態の確認
    • git statusで現在のリポジトリの状態を確認する
    git status
    
    • 未追跡のファイル(Untracked files:)の欄に作成したREADMEファイルが (赤色で)表示される
  5. 変更内容のステージング
    • コミットの一つ手前にステージングという段階がある
      • コミットしたい変更はステージングしておく
      • 逆に言えば,変更をコミットするためには, ステージングしておかなくてはならない
    • ファイルのトラッキング
      • 新しいファイルをステージングすると,これ以降, gitがそのファイルの変更を追跡するようになる
  6. ステージングの実行
    • 作成したREADMEファイルをステージングするには,次のコマンドを打つ
    git add .
    
    • 「git add」の「.(ピリオド)」を忘れないように
      • ピリオドは,リポジトリにおけるすべての変更を意味する
      • 複数のファイルを変更した場合には,ファイル名を指定して 部分的にステージングすることもできる…
        • が,このやりかたは好ましくない
        • 一度に複数の変更を行うのではなく,一つの変更を終えたら こまめにコミットする
  7. ステージング後のリポジトリへの状態
    • 再度,git statusコマンドで状態を確認しよう
    git status
    
    • コミットされる変更(Changes to be committed:)の欄に,READMEファイルが (緑色で)表示されれば正しい結果である
  8. ステージングされた内容をコミットする
    • ステージング段階にある変更内容をコミットする
    • コミットにはその内容を示すメッセージ文をつける
    • 「First commit」というメッセージをつけて新しいコミットを作成する
      • 「-m」オプションはそれに続く文字列をメッセージとして付与することを 指示するもの
    git commit -m 'First commit'
    
  9. コミット後の状態の確認
    • コミットが正常に行われたことを確認する
      • ここでもgit statusコマンドか活躍する
    git status
    
    • 「nothing to commit, …」との表示から コミットすべきものがない(=過去の変更はコミットされた)ことが わかる
    • この表示がでたら(無事コミットできたので)一安心してよい

2.1.4. 変更履歴の作成

  1. 更なるコミットを作成する
    • リポジトリで変更作業を行い,新しいコミットを追加する
      • READMEファイルに新しい行を追加する
    • 次の$NAMEをあなたの名前に変更して実行しなさい
    echo $NAME >> README
    
    • 既存のファイルへの追加なので「>>」を用いていることに注意
  2. 変更後の状態の確認
    • リポジトリの状態をここでも確認する
    git status
    
    • コミットのためにステージされていない変更(Changes not staged for commit:)の 欄に,変更された(modified)ファイルとしてREADMEが表示される
  3. 差分の確認
    • トラックされているファイルの変更箇所を確認する
    git diff
    
    • 頭に「+」のある(緑色で表示された)行が新たに追加された内容を示す
      • 削除した場合は「-」がつく
  4. 新たな差分をステージングする
    • 作成した差分をコミットできるようにするために,ステージング段階に上げる
    git add .
    
    • git status を行い,READMEファイルが「Changed to be commited:」の欄に (緑色で)表示されていることを確認する
  5. ステージングされた新しい差分のコミット
    • 変更内容を示すメッセージとともにコミットする
    git commit -m 'Add my name'
    

2.1.5. 履歴の確認

  1. バージョン履歴の確認
    • これまでの変更作業の履歴を確認
      • 2つのコミットが存在する
    git log
    
    • 各コミットごとに表示される内容
      • コミットのID(commit に続く英文字と数字の列)
      • AuthorとDate
      • コミットメッセージ
  2. コミットの情報確認
    • 次のコマンドでコミットで行った変更内容が確認できる
    git show $COMMIT_ID
    
  3. 2つのコミットの比較
    • 異なる2つのコミットの変更差分は次のコマンドで確認できる
      • コミットのIDはlogで確認できる (コマンドでIDを指定する場合は,概ね先頭4文字を入力し後は省略してよい)
    git diff $COMMIT_ID_1 $COMMIT_ID_2
    

2.1.6. ブランチの使い方

  1. ブランチとは
    • 「ひとまとまりの作業」を行う場所
    • ソースコードなどの編集作業を始める際には 必ず新しいブランチを作成する
    • Gitの内部的にはあるコミットに対するエイリアス(alias)である
  2. mainは大事なブランチ
    • Gitリポジトリの初期化後,最初のコミットを行うとmainブランチができる
    • 非常に重要なブランチであり, ここで 直接編集作業を行ってはならない
      • ただし,本演習や,個人でGitを利用する場合はこの限りではない
  3. ブランチの作成と移動
    • 新しいブランチ「new_branch」を作成して,なおかつ,そのブランチに移動する
      • 「-b」オプションで新規作成
      • オプションがなければ単なる移動(後述)
    git checkout -b new_branch
    
    • 本来,ブランチには 「これから行う作業の内容」 が分かる名前を付ける
  4. ブランチの確認
    • ブランチの一覧と現在のブランチを確認する
      • もともとあるmainブランチと,新しく作成したnew_branchが表示される
    git branch -vv
    
    • ブランチに紐づくコミットのIDが同じことも確認
    • git statusの一行目にも現在のブランチが表示される
  5. ブランチでのコミット作成
    • READMEに現在の日時を追加
    date >> README
    git add .
    git commit -m 'Add date'
    
    • 新しいコミットが追加できたことをgit logで確認
    • git branch -vvでコミットのIDが変化したことも確認
  6. ブランチの移動
    • new_branchブランチでコミットした内容をmainに反映させる
      • まずはmainに移動する
    git checkout main
    
    • git status,git branch -vvで現在のブランチを確認すること
    • この段階では,READMEファイルに行った変更が 反映されてない ことを 確認すること
  7. 変更をmainにマージ
    • new_branchで行ったコミットをmainに反映させる
    git merge new_branch
    
    • READMEに更新が反映されたことを確認
    • git branch -vvにより両ブランチのコミットIDが同じになったことも確認
    • git logも確認しておきたい
  8. マージ済みブランチの削除
    • マージしたブランチはもはや不要なので削除して良い
    git branch -d new_branch
    
    • git branch -vvコマンドで削除を確認

2.1.7. コンフリクト

  1. コンフリクトとは
    • ファイルの同じ箇所を,異なる内容に編集すると発生する
    • Gitはどちらの内容が正しいのかわからない
    • 次のシナリオに従い,コンフリクトを発生させてみよう
  2. コンフリクトのシナリオ
    • 「のび太」の作業
      • nobitaブランチを作成する
      • READMEファイルの 一行目 を「Nobita's README.」に変更する
      • 変更をaddしてcommitする
    • ここで一度, mainブランチ にもどる
      • READMEがもとのままだあることを確認
    • 「しずか」の作業
      • shizukaブランチを作成する
      • READMEファイルの 一行目 を「Shizuka's file.」に変更する
      • 変更をaddしてcommitする
  3. マージとコンフリクト発生
    • mainブランチに移動する
    • まず,nobitaブランチをマージ
      • 問題なくマージできる
    • 次に,shizukaブランチをマージ
      • ここで コンフリクトが発生 する
  4. コンフリクト時のメッセージ
    • mergeに失敗するとようなメッセージが出る
    Auto-merging README
    CONFLICT (content): Merge conflict in README
    Automatic merge failed; fix conflicts and then commit the result.
    
    • また,git statusするとUnmerged pathes:の欄に,「both modified: README」 と表示される
  5. READMEファイルの内容
    • READMEを開くとコンフリクトが起きた箇所がわかる
    <<<<<<< HEAD
    Nobita's README.
    =======
    Shizuka's file.
    >>>>>>> shizuka
    (以下略)
    
    • =======の上がマージ前のmainブランチ, 下がマージしようとしたshizukaブランチの内容
  6. コンフリクトの解消
    • テキストエディタで修正し,手動でコンフリクトを解消する
    Nobita & Shizuka's READMEfile.
    (以下略)
    
  7. 解消した結果をコミットする
    • その後はいつもどおり,addしてcommitすれば作業が継続できる
      • マージ済みのmainから新しくブランチを作成すること
    • なお, テキストエディタを用い,手動で正しくコンフリクトを解消する前でも commitできてしまう ので,この点には注意する

2.2. リモートリポジトリを利用する(GitHub)

2.2.1. GitHubとは

  1. GitHubでソーシャルコーディング
    • ソーシャルコーディングのためのクラウド環境
    • GitHubが提供する主な機能
      • GitHub flowによる協同開発
      • Pull requests
      • Issue / Wiki
  2. GitHubアカウントの作成
    • (まだなら)次のURLの指示に従いGitHubアカウントを作成
    • アカウントの種類
      • 無料版で作成する場合「Join GitHub for Free」を選択する
      • 学生の場合「Student Developer Pack」にアップグレードすることもできる
    • その後,確認メールが届くので,必要に応じて残りの手順を実施せよ
  3. SSHによるGitHubアクセス

2.2.2. リモートリポジトリ

  1. リモート VS ローカルリポジトリ
    • ローカルリポジトリ
      • git initコマンドを用いて作成したリポジトリを「ローカルリポジトリ」という
    • リモートリポジトリ
      • 「リモートリポジトリ」とは,サーバ上にあるリポジトリであり, ローカルのリポジトリと連携させることができる
    • リモートリポジトリの利点
      • ネットワークを経由してどこからでも利用することができる
      • 複数人のチームで協同作業をするときに活用できる
  2. リモートリポジトリの作成
    • リモートリポジトリをGitHubで作成する
      • 名前は「our_project」とする
    • 次の手順で作成する
    • READMEとライセンスを追加すること
      • 「Initialize this repository with a README」にチェックを入れる
      • 「Add a license:」から「MIT License」を選ぶ
  3. GitHub flowによる開発の流れ
  4. mainブランチとmasterブランチについて

2.3. GitHub flow

2.3.1. リモートリポジトリをローカルに複製

  • リモートにあるリポジトリをローカルに複製することをcloneという
  • 下記の「$GITHUB_URL」の部分をGitHubのour_projectリポジトリURLにして実行
    • URLは「git@…」で始まるSSH接続用のものを用いる
      • リポジトリのURLはブラウザ用のURLとは異なるので注意!
cd ~
git clone $GITHUB_URL
cd our_project
  • この作業は基本的にはプロジェクトに対して一度だけ行うこと

2.3.2. mainから作業用ブランチを作成

  • 作業用のブランチを作成して移動する
    • ブランチの名前は「greeting」とする
git checkout -b greeting

2.3.3. ブランチで編集作業を行う

  • ここでは,hello.txtという名前のファイルを作成する
echo 'Hello GitHub' > hello.txt

2.3.4. ブランチでコミットを作成

  • 変更した内容をステージングしてからコミットする
git add .
git commit -m 'Create hello.txt'
  • この編集,add,commitの作業は作業が一区切りつくまで何回も繰り返してよい…
    • が,こまめにpushするのが良いとされる

2.3.5. ブランチをリモートに送る

  • ブランチで作成したコミットをリモートに送る
    • 下記のoriginはリポジトリのURLの別名として自動で設定されているもの
    • greetingは作業しているブランチ名
git push -u origin greeting

2.3.6. GitHubでプルリクエストを送る

2.3.7. GitHubでレビュー(+自動テスト)

  • プルリクエストを用いたレビューの方法は下記参照
  • 人手によるレビューの他,自動的なテストも行うのが望ましい(説明は省略)

2.3.8. GitHubでプルリクエストをマージ

  • Pull requestのレビューが済んだらマージする
  • マージが完了したら,ローカル・リモート共に,マージ済みのブランチは削除してよい

2.3.9. ローカルのmainを最新版にする

  • GitHubで行ったマージをローカルに反映させる
    • mainブランチに移動してgit pull
    • 不要になった作業用ブランチは削除
git checkout main
git pull
git branch -d greeting

2.3.10. GitHub Flowに習熟する!   演習

  • ここで手順2:(2.3.2節)に戻り, 一連の作業を複数回(5回以上!)繰り返すこと
    • 体に叩き込む!

2.3.11. GitHub Pagesを作成する   演習

  • GitHubをWebブラウザで開く
  • プロジェクトの Settings -> Pages (side menu) でGitHub Pagesを開く
  • Sourceの項目にある Nonemain ブランチに変える
    • Save ボタンを押すとプロジェクト用のURLが表示されるので、控えておく
  • index.htmlファイルを作成しHTML書く
  • add/commit/pushを適切に行い、GitHubにindex.htmlをアップロードする
  • 先程控えたURLにブラウザでアクセスし、作成したHTMLによるページが表示されることを確認

3. チーム演習編

3.1. 演習のための準備

3.1.1. 概要

  • この演習では、最終的に全員でHTMLによるWebサイトを作ることを目指す
  • GitHub Pagesの機能を用いる
  • Webサイトの例
    • 架空の会社のホームページ
    • 架空の商品紹介ページ
    • 架空の…

3.1.2. リモートリポジトリの作成

  • チームの代表者1名がGitHubでリポジトリを作成する
    • 名前は「team_project」とする
  • 次の手順で作成する
  • READMEとライセンスを追加すること
    • 「Initialize this repository with a README」にチェックを入れる
    • 「Add a license:」から「MIT License」を選ぶ

3.1.3. コラボレーターの追加

3.1.4. リポジトリのclone

cd ~
git clone $GITHUB_URL
cd team_project

3.2. チーム演習

3.2.1. 課題1: GitHubのIssue/Wikiを学ぶ

  • リポジトリのIssue機能を使ってみよう
    • 一人1つIssueを登録する
    • メンバーのIssueに挨拶する(投稿する)
    • 終わったらIssueを閉じてみる
  • リポジトリのWikiを使ってみよう
    • Wikiを使ってチームメンバーの自己紹介をしてみよう
  • なお,この演習にあまり時間をかけてはならない

3.2.2. 課題2: まずは全員1回pushしよう

  • 全員1回,最初のGitHub Flowを成功させよう
  • GitHubのアカウント名($GITHUB_NAME)でブランチを切り,コミットするファイルを作る
  • ファイル名は各自の+「.html」とする
git checkout -b $GITHUB_NAME
# エディタで$GITHUB_NAME.htmlを作成
git add $GITHUB_NAME.html
git commit -m '<メッセージ>'
git push -u origin $GITHUB_NAME

3.2.3. 課題3: プルリクエストの作成

  • pushしたブランチからプルリクエストを出してみよう
  • 他のメンバーのプルリクエストにコメントしてみよう

3.2.4. 課題4: マージの練習

  • プルリクエストを出した人が自分でマージしてみよう
  • この段階でコンフリクトが出ることはないはず (同じファイルを編集していない)だが,もし マージできない場合は,プルリクエストを削除し, 課題2からやり直す

3.2.5. 課題5: 全員のmainを最新にする

  • 全員のローカルのmainブランチを最新にする
  • 各自次のコマンドを実行
git pull
  • ローカルに他のメンバーが作成したファイルができているか確認

3.2.6. 課題6: 再びブランチをpushする

  • まず,mainが最新版であることを確認
  • 作業を決めて, 作業の内容がわかりやすい名前 でブランチを作る
  • $GITHUB_NAME.html に,中身を追加してみよう(内容は何でも良い)
  • git add/commit/push を正確に実行しよう
  • ブランチが無事pushできたら,GitHubをブラウザで確認する

3.2.7. 課題7: 再びプルリクエストとマージ

  • 再びプルリクエストとマージを行ってみよう
  • プルリクエストのコメントには顔文字なども 利用できるので活用してみよう
    • やり方は各自で調べること
  • 今度は他のメンバーにマージを依頼しても良い

3.2.8. 課題8: 何回も繰り返す

  • 同じファイルに更なる変更を加え,GitHub Flowを回してみよう
  • これを最低3回は繰り返したい

3.2.9. 課題9: ぼちぼちコンフリクト

  • 誰かが空の「index.html」ファイルを作成する
  • 全員でindex.htmlを編集してみよう
    • $MY_FILEへのリンクを貼る
  • pushしてプルリクエストを出してみる
  • 何人かはコンフリクトになるはずだ

3.2.10. 課題10: コンフリクトの解消

  • コンフリクトが出たメンバーは,それを解消してみよう
  • コンフリクトが出なかったメンバーは,コンフリクトが出ているメンバーの 作業を見る
    • 困っていたら助けてあげよう

3.2.11. 課題11: Webサイトを作ってみよう

  • チームで内容を相談し,Webサイトを作ってみよう
  • index.htmlや$MY_FILE以外にもファイルを追加して 素敵なWebサイトを作ろう
  • 可能ならばCSSやJavaScriptを使っても良いが必須ではない

3.3. GitHubでのコンフリクトと解消方法

3.3.1. GitHub flow におけるコンフリクトについて

  • コンフリクトとは?
    • コンフリクトは、コードの同じ箇所を複数の人が別々に編集すると発生
  • コンフリクトが起きると?
    • GitHub に提出した Pull requests が自動的にマージできない

3.3.2. コンフリクトへの基本的な対処法

  • 初心者は、演習の最初の方では「他人と同じファイルを編集しない」こと にして、操作になれる
    • 上達したら積極的にコンフリクトを起こしてみて、その解決方法を学ぶ
  • コミットはできるだけ細かく作成すると良い
    • その分,他の人とかち合う可能性が減る

3.3.3. GitHubでのコンフリクトの解消

  • nobitaとshizukaのシナリオでnobitaの作業がGitHubでマージされたとする
    • shizukaが作成したPull requestはマージできなくなる
  • ローカルのmainブランチを最新版にする
    • pullを行う
  • shizukaブランチでmainの内容をmergeする
    • その後、手動でコンフリクトを解消
  • 再度shizukaをpushして、プルリクエストがマージ可能になれば成功

3.3.4. GitHubでのコンフリクトの解消方法(一例)

# ローカルのmainを最新に
git checkout main
git pull
# shizukaに移動してマージ
git checkout shizuka
git merge main
# ここでコンフリクトを解消する
git add .
git commit -m 'Merge'
git push -u origin shizuka

4. 参考資料

4.1. より進んだGitの操作

4.1.1. ファイルの削除と名前の変更

  1. Gitに無視させたいファイル
    • ツールが生成する中間ファイルなど,Gitで管理させたくないファイルは 予め「.gitignore」ファイルに記述しておく
    • なお,「.gitignore」ファイル自体はGitがトラッキングするファイルに含める
    • .gitignoreの書き方については各自で調べよ
  2. Gitが追跡するファイルの削除と名前の変更
    • Gitが追跡しているファイルであっても, シェルのrmコマンドやmvコマンドで削除や名前の変更をしてよい
    • 「git add .」コマンドを実行すると,Gitは削除や名前の変更も 自動的に検知する
      • 「git rm」や「git mv」は使わなくてよい

4.1.2. 操作を取り消すコマンド

  1. Gitで行った操作の取り消し
    • まちがって
      • ファイルをステージングさせた!
      • ステージングをコミットした!
    • などの場合,操作を取り消すことができる
      • 特定のファイルの変更の取り消し
      • 特定のコミットの取り消し
  2. HEADによるコミットの指定
    • 特定のコミットのIDを指定する方法に「HEAD」を使った相対指定がある
      • showコマンドで確認しながら用いると良い(下記はサンプル)
    git show HEAD~1
    git show HEAD~1^2
    
  3. ステージング/コミットの修正

    ファイルのステージングを取り消す

    git reset $FILE
    

    $COMMIT_IDより後のコミットの取り消し(ローカルは保存)

    git reset $COMMIT_ID
    

    $COMMIT_IDより後のコミットの取り消し(ローカルの変更も破棄)

    git reset --hard $COMMIT_ID
    
  4. 誤って編集や削除や修正したファイルの回復
    • file.txtを誤って編集や削除した場合
    • addする前
      • ステージング領域からの取り出し
    git checkout file.txt
    
    • addした後
      • 直近のコミットからの取り出し
    git checkout HEAD file.txt
    
  5. push済みのコミットの取り消し
    • 最後に行ったコミットが理由でコンフリクトが発生したような場合, 次の操作により,「取り消しコミット」を作成することができる
    git revert HEAD
    
    • 最後の作業が取り消されていることを確認
    • その後は,この取り消しコミットをpushすると, リモートでの変更内容も取り消される

4.1.3. 作業の一度中断と再開

  1. やりかけの作業のstash
    • あるブランチで作業中に他のブランチに 一時的に移動したいことがある
      • 作業の途中でmainブランチを最新にする,など
    • このような場合,git stashコマンドが活用できる ので調べてみよ

4.1.4. その他知っておくと良いコマンド

  1. ステージングしたファイルの差分表示
    • git addでステージングするとgit diffで差分が表示されない
    • この場合,次のコマンドで確認できる
    git diff --staged
    
  2. 特定のファイルにのみ関連する履歴確認
    • 特定のファイルの履歴のみ確認する
    git log --follow README
    
  3. リモートブランチの最新情報を取得
    • git fetchはリモートにあるブランチの最新情報を ローカルに取ってくるコマンド
    • 例えば,git pullはmainブランチで次の2つを実行することと同じ意味
    git fetch origin
    git merge origin/main
    
  4. git ls-files
    • 省略

Author: 中鉢 欣秀

Created: 2024-10-24 木 16:15

Validate