gitの基本的な使い方

  • 概要

gitの説明は多々あるが、複数のリモートレポジトリを使った操作について触れられているものは少ない。 そのため、ドキュメント化する。

  • シナリオ(こじつけ)

ほげ社ではgitを使ってhogewareというアプリを開発をしていた。 ある日、とある機能の開発を外注で頼んだ。

つまり、自社でもともとレポジトリを持っていたが、 あるタイミングでそのソースコードを外に出す。 そのあと、完成品を自分のレポジトリにマージしたい。 この差異、中間のcommit(変更履歴)はもらう必要がない。(もらったっていい)

  • 前提
$ git config --global push.default matching
  • 親プロジェクトをつくる
### ここからプロジェクトが始まる(レポジトリを作ります)
$ mkdir  -p repos/hogeware
$ cd repos/hogeware
$ git --bare init
  Initialized empty Git repository in /home/kanai/repos/hogeware/
### 作業用レポジトリを作りチェックアウト
$ mkdir ~/workdir/
$ cd ~/workdir/
$ git clone file:///home/kanai/repos/hogeware/
  Cloning into 'hogeware'...
$ cd hogeware/
### 最初のcommitをします(Changelogとsource1というファイルをcommit)
$ echo "init" > Changelog
$ echo "#include<stdio.h>" > source1.c
$ git add .
$ git commit -m "init"
### ここまででMasterができた。
### Branchを切ってticket1というパッチをつくる
$ git branch ticket1
$ git checkout ticket1
  Switched to branch 'ticket1'
$ cat "#include<stdlib.h>" >> source1.c
$ echo "#include<stdlib.h>" >> source1.c
$ echo "#include<stdio.h>" >> source2.c
$ git commit -m "working"
  On branch ticket1
$ git add .
$ git commit -m "working"
  [ticket1 ff36d86] working
### ticket1はまだ完成していなくて、もう少し変更を加えたとします
$ echo "ticket1 done" >> Changelog
$ git add .
$ git commit -m "finish #1"
  [ticket1 3cac89f] finish #1
### ticket1の変更が終わったのでmasterにmerge
$ git checkout master
$ git diff ticket1
 > あてるパッチを比較
$ git merge --no-ff ticket1 -m "merge ticket1"
 > master Branchに対して(=今のBranch)にticket1をmergeします。
### 以上では、masterブランチを開発用にしてきた。しかし、
### 今後、Developmentで開発する気になってきた
$ git branch development
$ git checkout development
 > このBranchはmasterからbranchしたものなので、
 > この時点ではmasterである
### せっかくなので、commit logをもう一個作る。
### Ticket2というBnrachを切ってdevelopmentにcommit
$ git branch ticket2
$ git checkout ticket2
$ echo "main(){printf("hoge");}" >> source2.c
$ echo "ticket2 finish" >> Changelog
$ git add .
$ git commit -m "finish #2"
### 終わったのでdevelopmentにmerge
$ git checkout development
$ git merge --no-ff ticket2 -m "merge #2"
### ここで、自分のリモートレポジトリに最新をpushする
### tagをきってpush
$ git tag 0.0
$ git push origin master
$ git push origin development
$ git push --tag
$ git branch -a
  * development
    master
    ticket1
    ticket2
    remotes/origin/development
    remotes/origin/masterxx
  • 開発先へのデプロイ

ここで、ほげ社は開発先もげ社に資材を渡す。 こちらではこちらでの管理をしたい。

@mieru2
### もげ社では、自社でレポジトリサーバを立てる。
### 通常のcloneでは、non-bareになるので、bareで作成する(push可能)
$ git clone --bare 192.168.9.99:~/workdir/hogeware hogeware
  Cloning into bare repository 'hogeware'...
### mieru2はレポジトリサーバであり、workdirも持つ。
### checkout用のディレクトリをつくり、cloneする。(これはnon-bare)
$ mkdir ~/workdir
$ cd ~/workdir
$ git clone file:///home/kanai/repos/hogeware
$ git branch -a
  * development

次に、もげ社は、宗教的にmasterを開発用にしているので、自分のブランチは development = masterにしたいとする。

$ git push origin :master
# developmentに入って、ここから派生を行う
$ git checkout development
$ git remote set-head origin development
 > masterを一度消したいので、headをdevelopmentに向ける
$ git branch -a
  * development
$ git branch -D master
  > masterを消す
  Deleted branch master (was 9d126d7).
$ git push origin :master
 > originからmasterを削除する
  To file:///home/kanai/repos/hogeware
   - [deleted]         master
$ git branch -m development master
 > masterをdevelopmentにrenameする
$ git branch -a
  * master
    remotes/origin/HEAD -> origin/development
    remotes/origin/development
    remotes/origin/ticket1
    remotes/origin/ticket2
### この時点では、localのmasterは(rename前の)origin/developmentをむいている
$ git push origin master
  Total 0 (delta 0), reused 0 (delta 0)
  To file:///home/kanai/repos/hogeware
   * [new branch]      master -> master
### HEAD(remote)の向き先を変える
  $ git remote set-head origin master
  $ git branch -a
  * master
    remotes/origin/HEAD -> origin/master
    remotes/origin/development
    remotes/origin/master
    remotes/origin/ticket1
    remotes/origin/ticket2
#$ git pull origin master
#    From file:///home/kanai/repos/hogeware
#     * branch            master     -> FETCH_HEAD
#    Already up-to-date.
### ちゃんとdevelopmentの内容が反映されている
$ tail Changelog
    init
    ticket1 done
    ticket2 finish
### このままだと、push先はorigin/developmentになる
$ git branch -vv
      issue101 6b0b1ad solve issue #101
      issue102 0ec50bc #102
    * master   d8bcc0e [origin/development: ahead 2] merge #101
      release  e5f1387 merge #2
    $
$ git push -u origin master
    Branch master set up to track remote branch master from origin.
    Everything up-to-date
$ git branch -vv
      issue101 6b0b1ad solve issue #101
      issue102 0ec50bc #102
    * master   d8bcc0e [origin/master] merge #101
      release  e5f1387 merge #2

これで、一応、developmentをmasterにすることができる。(やる必要があるのかは別の話) 開発側で少しいじってみる。開発側はreleaseというブランチで開発元にコードを引き渡したい。 とりあえず、release branchだけつくる。 ついでに、開発元でやってたpatchブランチはいらないので消す。

@mieru2
### いらないの消す
$ git branch release
$ git push origin release
  Total 0 (delta 0), reused 0 (delta 0)
  To file:///home/kanai/repos/hogeware
  * [new branch]      release -> release
$ git push origin :ticket1
  To file:///home/kanai/repos/hogeware
  - [deleted]         ticket1
$ git push origin :ticket2
  To file:///home/kanai/repos/hogeware
  - [deleted]         ticket2
$ git branch -a
  * master
    release
    remotes/origin/HEAD -> origin/master
    remotes/origin/development
    remotes/origin/master
    remotes/origin/release

ここで、remoteのHEADをかえる

### developmentを消そうとするとエラーとなる
### これは、remoteのorigin/HEADがdevelopmentをむいているため
$ git push origin :development
  remote: error: By default, deleting the current branch is denied, because the next
  remote: error: 'git clone' won't result in any file checked out, causing confusion.
  remote: error:
  remote: error: You can set 'receive.denyDeleteCurrent' configuration variable to
  remote: error: 'warn' or 'ignore' in the remote repository to allow deleting the
  remote: error: current branch, with or without a warning message.
  remote: error:
  remote: error: To squelch this message, you can set it to 'refuse'.
  remote: error: refusing to delete the current branch: refs/heads/development
  To file:///home/kanai/repos/hogeware
   ! [remote rejected] development (deletion of the current branch prohibited)
  error: failed to push some refs to 'file:///home/kanai/repos/hogeware'
### この様子を確認する
$ git remote show origin
  * remote origin
    Fetch URL: file:///home/kanai/repos/hogeware
    Push  URL: file:///home/kanai/repos/hogeware
    HEAD branch: development
    Remote branches:
      development tracked
      master      tracked
      release     tracked
    Local branch configured for 'git pull':
      master merges with remote master
    Local refs configured for 'git push':
      master  pushes to master  (up to date)
      release pushes to release (up to date)

いくつかやり方はあるが、repos側でtrack先を変える。

@mieru2: これはレポジトリ内での操作
$ cd ~/repos/hogeware/
$ git symbolic-ref HEAD refs/heads/master
@mieru2: 作業ディレクトリでremoteを確認する
$ cd ~/workdir/hogeware/
$ git remote show origin
  * remote origin
    Fetch URL: file:///home/kanai/repos/hogeware
    Push  URL: file:///home/kanai/repos/hogeware
    HEAD branch: master ★track先がmasterになっている
    Remote branches:
      development tracked
      master      tracked
      release     tracked
    Local branch configured for 'git pull':
      master merges with remote master
    Local refs configured for 'git push':
      master  pushes to master  (up to date)
      release pushes to release (up to date)
### これで、remote origin/masterはmasterなので、deleteする
$ git push origin :development
    To file:///home/kanai/repos/hogeware
     - [deleted]         development

折角なので、masterで開発してみる。

@mieru2
$ git branch issue101
$ git checkout issue101
$ echo "issue101 finish" >> Changelog
$ echo "int main(){}" >> source1.c
$ git add .
$ git commit
  Aborting commit due to empty commit message.
$ git checkout master
$ git commit -m "solve issue #101"
$ git merge --no-ff issue101 -m "merge #101"
  Merge made by the 'recursive' strategy.
   Changelog | 1 +
   source1.c | 1 +
   2 files changed, 2 insertions(+)

$ git branch issue 102
$ git checkout issue102
$ echo "102" >> Changelog
$ git add .
$ git checkout master
$ git commit -m "#102"
$ git merge --no-ff issue101 -m "merge #101"
$ git branch -D issue102
Deleted branch issue102 (was 0ec50bc).
$ git branch -D issue101
Deleted branch issue101 (was 6b0b1ad).

$ git push
   e5f1387..d8bcc0e  master -> master

ここで、開発先は、もう一台のPCに開発先のレポジトリを落とす。

@mieru3
### こちらは開発用なので、non-bare
$ git clone ssh://192.168.9.98//home/kanai/repos/hogeware
$ cd hogeware
$ git branch -a
  * master
    remotes/origin/HEAD -> origin/master
    remotes/origin/master
    remotes/origin/release

それぞれで競合しないパッチをあてて、masterツリーにpushしてみる。 ここでの開発方法はmaster=開発先に対して、localでmergeしてからpushする方法を考える。

### mieru2側での作業
@mieru2
$ git branch mieru2-101
$ git checkout mieru2-101
$ echo "/* 101 */" >> source1.c
$ git add .
$ git commit -m '101'
$ git checkout master
$ git merge --no-ff mieru2-101
$ git push
   5bc1c15..92e56e3  master -> master
### ここまでで、mieru2の変更は

### mieru3側での作業
@mieru3
$ git branch mieru3-102
$ git checkout mieru3-102
$ echo "/* 102 */" >> source2.c
$ git add .
$ git commit -m "102"
$ git merge --no-ff mieru3-102
$ git push
  kanai@192.168.9.98's password:
  To ssh://192.168.9.98//home/kanai/repos/hogeware
   ! [rejected]        master -> master (fetch first)
### これは、単に、最新とぶつかっただけ

### mieru3がわでmerge作業する
### というものの、conflictしていないので、単にmergeするだけ。
@mieru3
$  git fetch -p
     5bc1c15..92e56e3  master     -> origin/master
$  git merge --no-ff origin/master
   Merge made by the 'recursive' strategy.
$ git push
       92e56e3..3a79f95  master -> master

このように開発を行ったら、releaseブランチを切る。(受け渡し用)

@mieru2
$ git checkout release
$ git log | grep "commit " | wc -l
6
$ git merge --no-ff master -m "RELEASE-0.1"
$ git log | grep "commit " | wc -l
16
 > masterのlogツリーが取り込まれたことが分かる
$ git push
    e5f1387..137453c  release -> release
    e$ git tag 0.1
    $ git push --tags
    Total 0 (delta 0), reused 0 (delta 0)
    To file:///home/kanai/repos/hogeware
     * [new tag]         0.1 -> 0.1

ここで、開発先は開発が終わった。こんどは、開発元がこの変更を取り込む。

### mieru1
@mieru1
$ git fetch -p
# remoteのレポジトリを登録する
$ git remote add mieru2 ssh://192.168.9.98//home/kanai/workdir/hogeware
# mieru1側でmieru2のブランチをpullする
$ git fetch mieru2 release
   * branch            release    -> FETCH_HEAD
   * [new branch]      release    -> mieru2/release
$git fetch --tags mieru2 release
  kanai@192.168.9.98's password:
  From ssh://192.168.9.98//home/kanai/workdir/hogeware
   * branch            release    -> FETCH_HEAD
# mieru1でreleaseと
$ git merge --no-ff 0.1
   Merge made by the 'recursive' strategy.
$ git push
     e5f1387..92a3e59  development -> development