個人的Local環境のGit Branch運用について

Table of Contents

背景・動機

複数人で触っているRepoをLocalでGit操作する時、次のような問題が発生していた。

  • 直pushしてしまった
  • 意図しない作業ブランチで作業してしまった
  • コンフリクト解消ミスで本来必要なコードを消してしまう

GitHubのBranch RuleやCIなどでミスを防ぐべきだが、そういうのが整っていない環境というのは多々ある。 自衛のために個人的に行っているGit Branch運用について纏めておく。

当記事の前提は以下。

  • Git ClientはMagit
  • Default Branchは main
  • GitHubを利用している

試したこと・やったこと

0. 方針を決める

基本方針は以下。

  • 最新のRemote Branchを常に取得する
  • デフォルトがLocal Branchが一切ない状態にする
    • main Branchすら用意しない
  • 作業するタイミング のみ Local Branchを作成する
    • 作業が終わったら必ずGit Pushする(Remote管理)

1. 手元から一切のLocal Branchを消す

次のようなLocal Branchがあったとする。

$ git branch
   feature/xxx
   feature/yyy
   feature/zzz
*  main

origin/main に移動して、 main Branch含めすべてのLocal Branchを削除する。

$ git checkout origin/main

$ git branch -D main
$ git branch -D feature/xxx
$ git branch -D feature/yyy
$ git branch -D feature/zzz

次のような状態になるとよい。

$ git branch
* (HEAD detached at origin/main)

2. 最新のRemote Branchを取得

定期的にRemote BranchをFetchをする。

$ git fetch

~/.config/git/config に次のようにprune optionを付けておくことをオススメしている。

https://tracpath.com/docs/git-fetch/

[fetch]
prune = true
pruneTags = true

andrmuel/projectile-git-autofetch というEmacs Packageを使えば、開いてるプロジェクトを定期的にFetchすることが可能。

(autoload-if-found '(projectile-git-autofetch-setup) "projectile-git-autofetch" nil t)

(add-hook 'emacs-startup-hook #'projectile-git-autofetch-setup)

(with-eval-after-load 'projectile-git-autofetch
  ;; config
  (setopt projectile-git-autofetch-notify nil)
  (setopt projectile-git-autofetch-interval 60)
  (setopt projectile-git-autofetch-fetch-args '("--no-progress" "--prune" "--prune-tags")))

3. Localで作業をする場合

必ず origin/main からCheckoutする。

$ git branch
* (HEAD detached at origin/main)
$ git fetch

$ git checkout -b feature/xxx

ひととおり作業が終わったらGit PushしてPull Requestを出す。 Pull Requestを出してレビュー状態になったら origin/main にCheckoutして作業Branchを消す。

レビュー指摘を貰って修正する時はRemote BranchからLocal Branchを落としてきて作業をする。(以下ループ)

$ git checkout feature/xxx

4. Pull Request Reviewの場合

GitHub上のレビューで完結せず、手元で動作確認したい時はFetchした上でRemote BranchにCheckoutする。

$ git fetch
$ git checkout origin/feature/zzz

GitHub CLIを使えば簡単にCheckoutできる。 --detach オプションをつければRemote BranchにCheckoutできる。

$ gh pr checkout --detach 2191

5. Magitの場合

Magitのtransient menuを拡張して gh pr checkout コマンドを拡張した。 c p <pr-number> でRemote Branch、 c P <pr-number> でLocal BranchにCheckoutできるようになった。

(defun my/magit-gh-pr-checkout (pr-number detach)
  (let* ((args (append '("pr" "checkout")
                       (when detach '("--detach"))
                       (list (number-to-string pr-number))))
         (cmd (string-join (cons "gh" args) " ")))
    (message "Executing: %s" cmd)
    (apply #'call-process "gh" nil nil nil args)
    (magit-refresh)))

(defun my/magit-gh-pr-checkout-detach ()
  (interactive)
  (let ((pr (read-number "GitHub PR number (detach): ")))
    (my/magit-gh-pr-checkout pr t)))

(defun my/magit-gh-pr-checkout-normal ()
  (interactive)
  (let ((pr (read-number "GitHub PR number (branch): ")))
    (my/magit-gh-pr-checkout pr nil)))

(with-eval-after-load 'magit-branch
  (transient-append-suffix 'magit-branch "c"
    '("p" "Checkout PR (detach)" my/magit-gh-pr-checkout-detach))

  (transient-append-suffix 'magit-branch "c"
    '("P" "Checkout PR (branch)" my/magit-gh-pr-checkout-normal)))

得られた結果・所感

運用をはじめて半年くらい経ったが圧倒的にミスが減った。 特に最新の main Branchを取り込む作業が安全に行えるようになったのが良かった。

当初は運用コストかかるかなと思っていたが、Local Branchのケアが不要になってむしろコストが下がったのでよい施策だった。

今後の展開・検討事項

手元の環境はあくまで手元なので本質的な解決ではない。 本来はGithub(Remote)側で解決するべき問題なのでBranch Ruleなどで解決をしたい。

また、Terraform GitHub Providerを導入した のように、GitHubの設定を統一的にTerraform管理できるように理想的な運用を模索したい。