6 ポイント 投稿者 GN⁺ 2024-05-27 | 1件のコメント | WhatsAppで共有
  • Gitでよく詰まりがちな作業を、コミット作成、ブランチ移動、変更比較、リモート同期、変更取り消しのコマンドごとにすばやく引けるよう整理したPDF
  • ローカル作業フローは git statusgit addgit commitgit diff を中心に、ステージング前後の状態を確認してコミットを作る方式
  • ブランチ作業は git switchgit checkoutgit branchgit mergegit rebasegit cherry-pick作成・移動・マージ・付け替え を行う
  • 履歴の探索と復旧には git loggit blamegit showgit refloggit resetgit restore が含まれ、ブランチ・タグ・HEAD・リモートブランチもコミット参照として使える
  • リモートリポジトリ作業は git remote addgit fetchgit pullgit pushgit push --force-with-leasegit push --tags で変更を取得・送信する手順を扱う

リポジトリの開始と現在の状態確認

  • 新しいリポジトリを開始:
    • git init
  • 既存リポジトリを複製:
    • git clone $URL
  • 現在位置と状態を確認:
    • git status

コミットの準備と作成

  • 追跡されていないファイルを追加:
    • git add $FILE
  • すべての未追跡ファイルとステージングされていない変更を追加:
    • git add .
  • ファイルの一部の変更だけを選んでステージング:
    • git add -p
  • ファイルを削除または移動:
    • git rm $FILE
    • git mv $OLD $NEW
  • ファイルを削除せずに Git の追跡対象から外す:
    • git rm --cached $FILE
  • すべてのステージングを取り消し:
    • git reset HEAD
  • コミットを作成:
    • git commit
    • テキストエディタを開いてメッセージを作成
  • メッセージを直接指定してコミット:
    • git commit -m 'message'
  • ステージングされていない変更も含めてメッセージ付きでコミット:
    • git commit -am 'message'

ブランチの移動と管理

  • ブランチを切り替え:
    • git checkout $NAME
    • git switch $NAME
  • ブランチを作成:
    • git checkout -b $NAME
    • git switch -c $NAME
  • ブランチ一覧を見る:
    • git branch
  • 最新コミット基準でブランチを並べ替え:
    • git branch --sort=-committerdate
  • ブランチを削除:
    • git branch -d $NAME
  • ブランチを強制削除:
    • git branch -D $NAME

履歴の探索とコード追跡

  • ブランチ履歴を見る:
    • git log main
  • 2つのブランチの関係をグラフで見る:
    • git log --graph a b
  • 1行ログを見る:
    • git log --oneline
  • ファイルの各行を最後に変更した人を確認:
    • git blame $FILENAME
  • ファイルを変更したすべてのコミットを見る:
    • git log $FILENAME
  • 特定のテキストを追加または削除したコミットを探す:
    • git log -S banana

変更の比較

  • コミットとその親コミットとの差分を見る:
    • git show $COMMIT_ID
  • マージコミットとマージされた親との差分を見る:
    • git show --remerge-diff $COMMIT_ID
  • diff の要約を見る:
    • git diff $COMMIT_ID --stat
    • git show $COMMIT_ID --stat
  • 2つのコミットを比較:
    • git diff $COMMIT_ID $COMMIT_ID
  • 特定ファイルだけをコミットと比較:
    • git diff $COMMIT_ID $FILENAME
  • ステージング済み変更と未ステージング変更をまとめて比較:
    • git diff HEAD
  • ステージング済み変更だけを比較:
    • git diff --staged
  • 未ステージング変更だけを比較:
    • git diff

設定ファイルとオプション

  • ローカル Git 設定ファイル:
    • .git/config
  • グローバル Git 設定ファイル:
    • ~/.gitconfig
  • 無視するファイル一覧:
    • .gitignore
  • 設定オプションを指定:
    • git config user.name 'Julia'
  • グローバルオプションを指定:
    • git config --global ...
  • alias を追加:
    • git config alias.st status
  • 使用可能な設定オプションを確認:
    • man git-config

履歴の修正とファイルの復元

  • 最新コミットを取り消し、作業ディレクトリはそのまま維持:
    • git reset HEAD^
  • 失敗した rebase を戻す:
    • git reflog BRANCHNAME
    • git reset --hard $COMMIT_ID
  • 直近5件のコミットを1つにまとめる:
    • git rebase -i HEAD^^^^^^
    • まとめるコミットは pickfixup に変更
  • 別ブランチや別コミットのファイル版を取り込む:
    • git checkout $COMMIT_ID $FILE
    • git restore $FILE --source $COMMIT_ID
  • コミットメッセージを変更、または入れ忘れたファイルを追加:
    • git commit --amend

ブランチを統合する

  • 現在のブランチを別ブランチ側へ最新化する fast-forward merge:
    • git switch main
    • git merge banana
  • あるコミットを別ブランチへコピー:
    • git cherry-pick $COMMIT_ID
  • 分岐したブランチを rebase で統合:
    • git switch banana
    • git rebase main
  • 分岐したブランチを merge で統合:
    • git switch main
    • git merge banana
    • git commit
  • 分岐したブランチを squash merge で統合:
    • git switch main
    • git merge --squash banana
    • git commit

リモートリポジトリとの同期

  • リモートリポジトリを追加:
    • git remote add $NAME $URL
  • main ブランチを origin リモートへ push:
    • git push origin main
  • 初回 push のブランチを origin に上げて追跡ブランチを設定:
    • git push -u origin $NAME
  • 現在のブランチをリモート追跡ブランチへ push:
    • git push
  • force push:
    • git push --force-with-lease
  • タグを push:
    • git push --tags
  • 変更を取得、ローカルブランチは変更しない:
    • git fetch origin main
  • すべてのブランチを fetch:
    • git fetch --all
  • fetch 後に現在のブランチへ rebase:
    • git pull --rebase
  • fetch 後に現在のブランチへ merge:
    • git pull origin main
    • git pull

コミットを指す方法

  • $COMMIT_ID の位置にはさまざまな参照を使える:
    • ブランチ: main
    • タグ: v0.1
    • コミット ID: 3e887ab
    • リモートブランチ: origin/main
    • 現在のコミット: HEAD
    • 3つ前のコミット: HEAD^^^
    • 3つ前のコミット: HEAD~3

変更を破棄する

  • 1ファイルのステージング済み変更と未ステージング変更を両方削除:
    • git checkout HEAD $FILE
  • 1ファイルの未ステージング変更を削除:
    • git checkout $FILE
  • すべてのステージング済み変更と未ステージング変更を削除:
    • git reset --hard
  • ステージング済み変更と未ステージング変更を stash:
    • git stash
  • 未追跡ファイルを削除:
    • git clean

1件のコメント

 
GN⁺ 2024-05-27
Hacker Newsの意見
  • 隠れた逸品のようなコマンドがいくつかある: git diff --staged は最後のコミットと現在ステージングした内容との差分を表示してくれるので、ファイルやハンクを細かく追加するときに意図した内容だけが入っているか確認するのに便利
    git log <file>はそのファイルが変更されたコミットだけを表示してくれるため、全体のgit logを探し回る必要が減り、特に頻繁には変わらないファイルで有用
    git add --patchを使うとき、Gitが分割したハンクよりもさらに細かく選びたいなら、eオプションを試すとよい。個々の行を含める/除外する方法が案内されるので、覚えておく必要はない

    • git diff --stagedgit diff --cachedのエイリアスだと今知った。cachedを何年も使ってきたが、stagedのほうがずっと直感的ではある。ただ、この筋肉の記憶を消せるかどうかは分からない
    • こうしたコマンドをエイリアスとしてたくさん作っておくと、かなり時間を節約できるし、作業をよりきちんと整理するハードルも下がる
      興味があれば見てもいいし、エイリアスを提案してもよい: https://gitlab.com/Falimonda/gitrc
    • git config --global commit.verbose trueを設定すると、Gitがコミットメッセージエディタのコメント領域にステージング済みの差分を自動で入れてくれる
  • 一覧にはないが、好きなコマンドの一つは git log -LstartLine,endLine:fileName
    拡張されたgit blameのように、指定した行が時間とともにどう変わり、誰が変えたのかを表示してくれる。行番号が変わったりファイルが移動したりしても追跡する
    「誰が、いつ、なぜこうしたのか?」や「いつからこうなっているのか?」といった質問に答えてくれる

  • 「コンピュータ科学には難しいことが2つだけある。キャッシュの無効化と名前付けだ」というPhil Karltonの言葉を思い出す
    そういう観点では、Gitコマンド名が**blame**なのはかなり変に感じたし、そう思ったのは自分だけではなかった
    What does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
    Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
    Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
    Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/

    • gitというツール名を考えると、blameブランドに合った名前のようにも思える
    • 同意。私は設定blameを**who**に変えた。実際にはVimの筋金入りユーザーなので、単にwとして使っている
  • Gitを学んだ方法は気に入っている。最初はサードパーティ製Git GUIのSourcetreeから始めたが、使うのも理解するのもとても簡単だった
    その次にgit guiへ移ったが、この段階は飛ばしてもよい。その後、UIを開くのが面倒になってターミナルコマンドを覚えた
    Git初心者にはおすすめするが、あくまで自分のn=1の経験にすぎない

    • Gitについて知っているのはclonepullcommitpushくらいで、これまではそれで十分だった。Gitはそれくらい単純にもなり得る。もちろん必要なら、より複雑な使い方も学べる
    • ターミナルコマンドに戻りたいとは思わない。Gitリポジトリとワークフローは可視化したほうがずっと理解しやすいので、Gitは実質的にGUIで扱うことを求めているツールに近い
      Magitを使い始めた瞬間、Gitが理解できるようになった
    • 私はSourcetreeからForkへ移った。非常によく似ているが、性能がはるかに良く、UIも滑らか。ここ数年でSourcetreeが改善されていなければ、だが
    • 似たような道筋だったが、GitKrakenとSublime Mergeを使い、複雑な作業も簡単にしてくれたのでSublime Mergeは使い続けている
      Sublime Mergeは褒めることしかない
  • Git が不必要に複雑なのには歴史的な理由があるのか気になる
    このチートシートを見ると、その複雑さをさらに実感する

    • その通り。Git は Linus Torvalds が Linux カーネル開発を可能にし、発展させるために作ったツールだから
      ここまで広く使われるようになったのは「幸運な事故」か、あるいは不幸かもしれない。GitHub が「無料の Git ホスティング」で Git を普及させた功績は大きいが、Git が多くのプロジェクトにとって理想的なバージョン管理システムだとは思わない
      Linus と Linux のために作られたので、用語やワークフローが一般的な他のバージョン管理システムとは逆に感じられることがある。Pull Request という名前も、Linux 開発で Linus やメンテナーが変更をカーネルツリーへ取り込むから。ほかのシステムでは通常 Merge Request と呼び、どこから来た変更であれツリーにマージするという点で、そちらのほうがずっと論理的
      Git は巨大なコードベースを高速に扱える、ほぼ唯一のオープンソースの分散バージョン管理システムだったので、大企業でも慣性が大きくなった
      個人プロジェクトでは Mercurial のほうをずっと好んで実際に使っているが、Android AOSP のような大きなコードベースで Hg を使ってみると、かなりもたつく。Mercurial はユーザーインターフェイスとコマンド体系がより良く、変更ツリーを見るためにその場で Web サーバーを立ち上げる機能もあるが、Python で書かれているため、性能とスケーラビリティは理想的ではない
    • 複雑さの一部は、初期バージョンで新しいサブコマンドを追加するコストがかなり高かったため。その結果、git checkout のような既存のサブコマンドに機能が次々と追加され、ユーザー体験がより混乱するようになった
      最近の Git では新しいサブコマンド追加のコストが下がり、git switch のように、より具体的なコマンドを追加し始めている
    • ほとんどのワークフローでは、logaddcommitpullpush のようないくつかの Git コマンドだけ知っていれば十分だと思う。GUI ツールもあるし
      正直、一般的な作業では Git がそれほど複雑だとは思わない。ただ、必要なときに使える強力な機能がたくさんあるだけ
    • Git は不必要に複雑なわけではない。Git がサポートするために作られたワークフロー自体が特に複雑で、対象ユーザーであるカーネル開発者は非常に技術寄りだったため、複雑さは大きな問題ではなかった
      カーネル開発者は分散型のやり方で互いにやり取りし、互いの作業の上にさらに作業しなければならなかった。15,000 人がコードを書くプロジェクトでどう働くのか? Git はその目的のために作られた
      設計対象のワークフローを支えるのに必要なだけ複雑。もっと単純なツールを作るのは簡単だが、そうすると Linux カーネル開発のような複雑なワークフローは支えられない。だから不必要に複雑なのではないが、単純な用途には必要以上に複雑なのは確か
      もっと複雑でないフロントエンドが必要なら、Git を扱いやすくしてくれるツールはたくさんある。専業のソフトウェア開発者でないなら、Git を直接使うことはおすすめしない。だが専業開発者なら、内部概念を理解するために時間をかける価値があり、いったん理解すれば、Git はやっていることのわりにかなりエレガントだ
      Git を知っているかどうかでソフトウェア開発者の資格を線引きするつもりはないが、こうした基盤システムがどう動くのかを自分で把握することにドーパミンを感じないなら、この分野は好みではないのかもしれない。それでも構わないし、誰にでも向き不向きがある
      Git の移動を学ぶには https://learngitbranching.js.org をおすすめする。もちろん https://xkcd.com/1597/ も当てはまる
    • 良い UI 設計は別のスキルで、追加の労力が必要なので、たいていは単に無視されるから
  • git add -p を知らなかったが、本当に便利
    -p--patch は、インデックスと作業ツリーの間のパッチハンクを対話的に選んでインデックスに追加できるようにする。修正内容をインデックスに追加する前に差分を確認する機会を与えてくれる

    • 可能なら、現在の作業のハンクを自分で選んで一貫したコミットとして組み立てる。こうすると、自分がやったことを頭の中で整理し直す助けになり、誰かに説明する必要があるときもコミットの流れに沿って説明できる
  • ここに追加したいのは、git commit --fixup $COMMIT_IDgit rebase -i upstream/master --autosquash の組み合わせ
    最近のワークフローでは必須要素になっている。Julia の例では直前の 5 コミットをリベースするために HEAD^^^^^ を使っているが、私は最近まで HEAD~5 を使っていた。ところが今では、upstream の HEAD までのすべてのコミットをそのままリベースできることが分かった

    • git commit --fixup $COMMIT_ID はすぐに取り入れたい。私はいつも “oops GET users/me” のような小さな修正用コミットを作り、次の対話的リベースで手動で移動して fixup していたが、このやり方のほうがずっと良い
    • これを最近ようやく覚えたが、Gerrit でスタック型パッチとして作業するときにとても良い
  • 「最後の 5 コミットを 1 つにまとめる: git rebase -i HEAD^^^^^^」は git rebase -i HEAD~5 が正しいのではないかと思う
    線形履歴だけをリベースしたい場合なら、という前提だが
    https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git

  • 最大の気づきは、すべてのコミットがまだ内部に残っていると理解した瞬間だった。タグやブランチは単にコミットを指しているだけで、ブランチが以前指していたコミットも消えたわけではない
    多くのコマンド、たぶんほぼすべてと言っていいほどのコマンドは非破壊的なので、ブランチの以前の状態を簡単に参照できる
    git reflog は見たことがあったが、主に「前に作業していたあのブランチは何だったっけ?」と思い出す用途にしか使っていなかった
    望まないコンフリクト解決の真っただ中に入り込む可能性のある操作に対して、半ば健全な恐怖心を持つようになった。たいていは間違ったコミットやブランチを参照してコンフリクトが起きていたからだ
    その恐怖が完全になくなったわけではないが、少なくともリベースのような操作の途中で以前の作業を復旧することにはかなり慣れた

    • Jujutsu はそうした恐怖を完全に取り除いてくれた
      ある操作の後にコンフリクトが起きるか分からなければ、とりあえずやってみて確認する。コンフリクトが起きたら解決するか、コンフリクトを作った操作を取り消すか、別の場所に新しいコミットを作ってコンフリクトは後で処理するようにしておく
      コンフリクトが起きると分かっているなら、関連するブランチを複製してから事前に合わせてみるか、コンフリクトする操作を実行して解決手順を試してみる。解決が面倒になったら一部の解決だけを取り消して試し続けるか、いったん保留して別の作業をするか、ツリー全体を捨てることもできる
  • 率直に言えば、Gitはいったん置いて JJ を見るべきだ
    https://martinvonz.github.io/jj/v0.17.1/
    LinusでさえGitのUIはひどいと思っているはずだ。JJのワークフローは素晴らしい

    • インデックスがない点には同意しにくい。そのほうが単純になるワークフローもあるだろうが、私は良くも悪くもGitのインデックスに慣れすぎていて、作業のやり方がかなり大きく変わりそうだ
      ただ、少し触ってみた以外に実務で深く使ったわけではないので、断定はしにくい