git, github

Page content

自分で作るコードで、公開できるものはなるべくgithub経由で管理したい。そうでなくてもbitbucketとかが使える。さらにそうでなくても GitLab, 自前サーバ, …。 そうすることでどこでも開発できるしバックアップにもなる。と、言うことでその手のリポジトリ管理に使えるtipsをまとめる。

git 管理のワークフロー

すべてのコミットを一直線で管理すると、一度の機能を開発していたかわからない。ブランチを切って、そのブランチの履歴を残すことで、あとからブランチでどんな開発をしていたか把握したい。 github でもプライベートの git repository でも使える方法にしたい。

様々なワークフロー、ブランチ戦略

POSTD - GitLab flowから学ぶワークフローの実践 などを参考にすると良い。有名なのは以下3つかな:

  1. git flow: スタンダード?思想は1つ1つ聞けば分かるし納得感はある。でも複雑だと言われていて、たしかに遵守するのは骨が折れそう。

  2. github flow: 小規模チームやプロダクト、頻繁なリリースが求められるプロジェクトに向くと言われる。

  3. gitlab flow: これも注目。

3つの対比をうまく残したいな。

整理しきれていない tips (ワークフローによっては当てはまらないだろう)

git-flow コマンド実践

  • local での開発

    • リポジトリの取得
      • 初回: git clone <repo> && cd <repo_dir>
      • ほか: cd <repo_dir> && git pull
    • branch の確認
      • git branch: ブランチの一覧確認、現在のブランチの確認
    • branch 作成
      • git branch <branchname>: でブランチ作成
    • branch 切り替え
      • git checkout <branchname> でコミットするブランチを切り替える (チェックアウトする)
      • git checkout -b <branchname>: コレで一発で作成&移動というのも場合によってアリ
    • コード書く
    • ステータス確認
      • git status
    • 差分確認
      • git diff
    • 対象をステージして、コミット
      • git add <files>
      • git commit -m "<commit_message>"
      • git commit --amend: 必要ならこのコマンドでメッセージを修正可能
    • リモートリポジトリにプッシュ
      • そのブランチを初めてプッシュするとき: git push --set-upstream origin <branchname>
      • 2回目以降: git push
  • github, gitlab, サーバ上の bare-repository で

    • no fast-forward なマージを実施
      • github, gitlab等なら web UI で プルリク + マージ。
      • bare-repo
        • cd <pare-repo-dir>
        • git checkout master: merge 先 (通常は master) のブランチに移動
        • git merge --no-ff <branchname>: no fast-forward でマージ
    • branch を削除
  • local

    • マージしたブランチを削除
    • master を pull

pull じゃなくて fetch & merge 使えという話

  • マスターに置いていかれた (branch切って開発している間に、別のbranchがマージされてマスターが進んでしまった場合) - git fetch origin - master で git merge origin/master

リポジトリの新規作成方法

色々パターンあるよね

ローカルのプロジェクトから github にリポジトリを新規作成する

  1. localで管理されているプロジェクトを用意する。

  2. おもむろにgithubにsign inしてrepositoryを新規作成する

  3. 仮にユーザ名がgeorgeで myproject というプロジェクトを作ったすると、ローカルからのpushは下記のような感じになる。

$ git remote add origin git@github.com:george/myproject
  1. これで以下のように-u付けてpushすれば次回以降もpull,pushが楽
$ git push -u origin master
Warning: Permanently added the RSA host key for IP address '192.30.252.130' to the list of known hosts.
Counting objects: 465, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (442/442), done.
Writing objects: 100% (465/465), 84.71 KiB | 0 bytes/s, done.
Total 465 (delta 244), reused 0 (delta 0)
To git@github.com:take4mats/revreport
* [new branch] master -> master

完了!

ローカルのプロジェクトから 自分のサーバにリポジトリを新規作成する

シナリオ: よくサーバ上の本番環境でアプリを書いてしまっているが、サーバにベアリポジトリを設置→ローカルにcloneして開発→ベアリポジトリにpush→本番環境でpullして反映、としたい

  1. (サーバ側) もともとあるgit project: ~/myproject

  2. (サーバ側) ベアリポジトリの集積場を作成:

    $ mkdir ~/repository
    
  3. (サーバ側) ベアリポジトリを作成:

    $ git clone --bare ~/myproject ~/repository/myproject.git
    
  4. (サーバ側) 元プロジェクトにリモートリポジトリを登録(ベアリポジトリを指定):

    $ git remote add origin /home/user/repository/myproject.git/
    
    • 同じサーバ内なので/home/…となっていますが、リモートサーバを指定するときは、パスの先頭に git:// とか ssh:// って感じ
  5. リモートリポジトリの登録を確認:

    $ git remote -v
    origin    /home/user/repository/myproject.git/ (fetch)
    origin    /home/user/repository/myproject.git/ (push)
    
  6. origin masterに git push だけで push してくれるよう、 upstream を登録する:

    $ git push -u origin master
    
    • .git/configに以下が追加されているはず
    [branch "master"]
        remote = origin
        merge = refs/heads/master
    
  7. (クライアント側) ベアリポジトリからクローンしてプロジェクトを開始!

    $ git clone ssh://user@remotehost.com/home/user/repository/myproject.git
    
  8. (クライアント側) git remote -v してみるとちゃんとベアリポジトリがリモートリポジトリに登録されているのがわかる

git config を プロジェクトごとに 切り替える方法

個人で複数アカウント持っていたり、お仕事で使っていたりで、アカウント切り替えたいっていうこともあるよね。きっと。

  • グローバルに入れる設定は以下だけど、

    git config --global user.name "defaultname"
    git config --global user.email "default@example.com"
    
  • 各リポジトリ(ディレクトリ)で以下を打てばそっちが優先されるよ

    git config user.name "projname"
    git config user.email "proj@example.com"
    
  • 1つ1つのディレクトリに profile 個別指定よりも、「ある親ディレクトリ(仕事dirなど)配下の git repository は一律仕事の profile を指定したい」満たないことも出来ます。

    • ~/.gitconfig で
      [includeIf "gitdir:~/shachiku/"]
      path = ~/.gitconfig_shachiku
      
    • そして ~/.gitconfig_shachiku を作ってこんな風にユーザプロフィールを書く、と。
      [user]
          name = shain-a
          email = zangyo.daisuki@black.com
      
    • この方法の注意点: .gitconfig で user profile が書かれているとグローバルにその設定が聞いてしまうので、ディレクトリごとにならない。触るディレクトリに対してはすべて定義が必要。オススメは、 homedir 直下でプロフィールごとに sub dir をきって、それごとに profile を定義する。これで漏れがなくせそうな気がします。 (/usr/local/hoge で作業?知らん)

hooks で git コマンド使うときは --git-dir=.git とかしないとだめ

  • 例: bare-repository に書いた post-receive で、 サーバサイドの repository で pull する
    #!/bin/sh
    
    cd /home/fnobi/sites/my-website
    git --git-dir=.git pull
    
    ちなみに無いと fatal: Not a git repository: '.' と怒られました

branch 操作系

  • 新しいリモートブランチをローカルに持ってくる

    • pull は現在の local branch にマージされてしまうから使ってはいけない!ので代わりにこうする:
      $ git branch <newbranch> origin/<newbranch>
      
      # もしくはこうする:
      $ git checkout -b <newbranch> origin/<newbranch>
      
  • 新しいローカルブランチをリモートにプッシュする

    • まずローカルでブランチを切ってチェックアウト
      $ git branch <newbranch> <origin/branchname>
      $ git checkout <newbranch>
      
      # もしくはこうする:
      $ git checkout -b <newbranch> <originalbranch like origin/master>
      
    • 続いて変更を加えてコミット
      git add <updated files>
      git commit -m '<commit message>'
      
    • 新しいブランチを作るようにプッシュする
      git push -u origin <newbranch>
      
  • ローカルブランチをリモートのブランチ名を明示してプッシュする

    • ローカル master ブランチを originにある上流ブランチに push する。
      git push origin master
      
    • コロン を使えば、ローカルブランチだけでなく、リモートブランチ名を明示できる
      # ローカルのブランチ hoge のコミットを、origin 上の dev ブランチに push する
      git push origin hoge:dev
      
  • local/remote branch を rename する

    # ローカルブランチ名の変更
    $ git branch -m hoge fuga
    # あるいは、今いるbranchをリネームするならこれ:
    $ git branch -m fuga
    # リモートブランチを削除
    $ git push origin :hoge
    # ブランチ名を変更したローカルbranchをプッシュしよう
    $ git checkout fuga
    $ git push origin fuga
    

pager を個別指定したい

自分は pager がデフォルトで less になっているんだけど、そのせいか git log -1git branch -agit config --list もチルダで画面を埋め尽くされてしまう。 と思ったらできた。 sh git config --global pager.{branch|log|config など} cat

git log の表示形式をカスタマイズ

うまく使いこなしたいところだが、今のところはコピペで。

  • イケテルと思ったサンプル2つ (decorate, pretty のパターンそれぞれ)

    # こっちのほうが簡潔だし使い回しやすい
    git log --graph --name-status --decorate --oneline
    # こっちは色とcommitterが見やすいけど開業が余計でうまく消せてない
    git log --graph --name-status --pretty=format:"%C(red)%h %C(green)%an %Creset%s %C(yellow)%d%Creset%n"
    
  • alias に登録して使おうかな

    git config --global alias.log-decorate 'log --graph --name-status --decorate --oneline'
    git config --global alias.log-pretty 'log --graph --name-status --pretty=format:"%C(red)%h %C(green)%an %Creset%s %C(yellow)%d%Creset%n"'
    
  • 今後

    • commit, timestamp, committer, message \n changed files が並んでいて、グラフっぽくなっていて、色もほどほどに使われたものにしたい
  • 参考

rebase

あるブランチが発生(枝分かれ)した、そのもと (base) となるコミットを変更すること。

例: dev ブランチが master ブランチから作成され、その後 master ブランチが更新されていたとして、その更新に dev が追いつくときは以下のような感じになる:

git checkout dev
git rebase master

もしそこで conflict が発生すれば、以下のような感じで解決する:

# conflict が発生しているファイルを手動で更新して解決
git [ add | rm ] <そのファイル>
git rebase --continue

conflict は commit 履歴の単位で繰り返しチェックされるので、何度もこれを繰り返すことがあり得る。注意して辛抱強くやる必要がある。つまり、なるべくやりたくない。

rebase -i でコミットをまとめる

サルでもわかるGit入門 - 5. rebase -i でコミットをまとめる にあるとおりだが、

# 直近3つのコミットをまとめる
git rebase -i HEAD~3

# すると vim で以下のように対象コミットが並んで出る
  pick ab82f29 del c
  pick 58aae43 add a
  pick 3a2b1be add b

# 消したいものを pick の部分を s に置き換える。一番古い commit は残す必要がある。
  e ab82f29 del c
  s 58aae43 add a
  s 3a2b1be add b

# これで保存・終了する

# rebase を再開
git rebase --continue

# コミットのコメントの編集画面に再度入る。ここでコミットを1つにまとめる分、対応するようにコメントを書き直すと良い
  del c, add a,b

# これで完了

これと git push -f を組み合わせれば、リモートリポジトリに push してしまっている細かい commit も綺麗にまとめることができる (ただし push -f は慎重に。共同編集している場合は勧められない)

conflict 解消の一手順

  • 前提
    • master と feature で conflict が発生し、手動で編集しないと feature をマージできない
    • どうも master のほうが新しいものも多い
  • 対応
    • 絵でいうとこんな感じ (作図途中) branch-diagram
    • コマンド
      # B の位置で、まず feature に master での更新を取り込もうとする
      git checkout feature
      git merge master
      # conflict を解消しようとする
      vim hogehoge
      # 一人で難しければ一度 commit してしまう
      git add .
      git commit -m 'merge to catch up'  #=> C
      git push
      # その後に feature の古い commit と diff 取りがら直してもらう  #=> D
      # その後 feature => master のマージの PR を作成  #=> E
      

stash で作業を一時保存する

  • 動機: master で作業してしまっていることに途中で気づく、その作業をリセットしてやり直しはしたくないが、別ブランチ (feature/hoge) で commit したい。
  • 解法 (commit 前)
    (git reset などで変更したファイルを git add する前の状態にする)
    git stash save
    git stash list
        #=> stash@{0}: WIP on master: <HEADのコミット情報>
    git checkout feature/hoge  # コミットしたい先の branch
    git stash pop stash@{0}
    
  • 参考: [Git]誤ったブランチで実施した変更を正しいブランチに移動する | Developers.IO

理論

お勉強に

ブランチの種類 (local, remote, tracking)

  • local branches: 自分がまさに編集する対象のブランチ
  • remote branches: リポジトリ (github 等) 上にあるブランチ。 origin とかよくついている
  • tracking branches: ローカルにあって (なのでローカルブランチの一種)、リモートブランチの状況を追跡するためのブランチ。 git fetch origin とすると全てのリモートブランチの最新状況をとってくる。

git fetch

リモートリポジトリの情報を取得する。手元のブランチに反映するにはマージが必要

git show

前回のコミットの差分を表示してくれる.

Fast-Forward

merge の時に ff しないように予め設定しておく主義もあるらしいですよ。

git config --global --add merge.ff false
git config --global --add pull.ff only

git tag

  • タグを打つ: git tag <tag_name> [-m <message>] [<commit(Default latest)>]
  • タグの確認: git tag
  • タグの削除: git tag -d
  • タグの共有: git push origin <tag_name>
    • tagは作成しただけではリモートには反映されないのでpushしてやる必要があるんだよ!!!