Git学习笔记

Contents

  1. 1. 配置
  2. 2. 忽略文件
  3. 3. 添加文件到暂存区
  4. 4. 移除文件
  5. 5. 移动文件
  6. 6. 提交修改
  7. 7. 保存进度
  8. 8. 查看工作目录状态
  9. 9. 查看文件改动
  10. 10. 查看提交信息
  11. 11. 撤销操作
  12. 12. 反转提交
  13. 13. 克隆远程版本库
  14. 14. 版本库同步
  15. 15. 分支
  16. 16. 创建分支
  17. 17. 切换分支
  18. 18. 重命名分支
  19. 19. 合并分支
  20. 20. 删除分支
  21. 21. 变基
  22. 22. 打标签
  23. 23. 创建归档文件
  24. 24. 文件重命名和移动
  25. 25. 合并其他remote上面的更新

配置

Git 配置文件 .gitconfig,系统配置位于/etc/gitconfig(Windows 该文件在Git安装目录下),全局配置位于用户目录下,项目配置位于仓库目录下。

1
2
3
git config --list                 # 显示当前的Git配置
git config user.name # 显示当前配置项的值
git config -e [--global|--system] # 编辑Git配置文件

配置别名

1
2
3
4
5
6
7
git config --global alias.ci "commit"
git config --global alias.co "chechout"
git config --global alias.st "status"
git config --global alias.br "branch"
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual '!gitk'

如果想要执行外部命令,而不是一个 Git 子命令,可以在命令前面加入 ! 符号。

忽略文件

如果是版本库级别的忽略用.gitignore;如果仅仅是个人的忽略用.git/info/exclude,不会将此设置传播出去

文件 .gitignore 的格式规范如下:

  1. 所有空行或者以 # 开头的行都会被 Git 忽略。
  2. 可以使用标准的 glob 模式匹配。
  3. 匹配模式可以以(/)开头防止递归。
  4. 匹配模式可以以(/)结尾指定目录。
  5. 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号(*) 表示匹配任意中间目录,比如a/**/z 可以匹配 a/z, a/b/za/b/c/z等。

1
2
3
4
5
6
7
# 此为注释 – 将被 Git 忽略
*.a # 忽略所有 .a 结尾的文件
!lib.a # 但 lib.a 除外
/TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目录下的所有文件,包括 subdir/build/
doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/**/*.pdf # 忽略所有doc 目录下的 pdf文件

添加文件到暂存区

  • 基本命令 git add
  • 交互模式 git add -i
  • 补丁模式 git add -p

交互模式下如果出现两个>时,什么都不选,直接敲回车可以回到上一级。

1
2
3
4
5
$ git add -i
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>

移除文件

从暂存区移除,并从工作目录中删除

1
git rm <filename>

从暂存区移除,保留工作目录中的文件,文件仍在磁盘中,但是git不再跟踪文件

1
git rm --cached <filename>

移动文件

1
git mv <oldfile> <newfile>

提交修改

先暂存,后提交

1
2
git add files
git commit -m ".." # 需要注意的是如果没有-a参数或者指定文件,将只是提交暂存区的内容

跳过暂存直接提交 git commit -m ".." -a; 或者 git commit -m ".." files

提交时显示diff信息git commit -v

  • 多个-m可以换行
  • 提交留言至少应该体现出进行本次修改的原因
  • 增补提交
    对于提交后发现一些小问题的情况,可以使用–amend。只能针对最后一个提交。例如:编辑contact.html添加一个链接 git commit -m "add link to google" -a。之后修改链接地址 git commit -C HEAD -a --amend-C使复用指定提交(HEAD)的提交留言。-c会打开默认编辑器。增补提交是修正上次提交,git log 会发现只有增补提交的提交名称,而没有上次的。

保存进度

Git 不允许在当前分支处于修改状态时,切换到其他分支。此时可以用git stash命令来保存当前分支修改进度。

1
git stash # 保存进度,会分别对暂存区和工作区的状态进行保存。

切换分支,做一些提交,然后切回来,然后执行下面命令可以恢复之前进度

1
2
3
4
# 查看保存的进度
git stash list
# 从最近保存的进度进行恢复,只恢复工作区的进度,恢复后会删除保存的进度。添加`--index`参数,恢复工作区和暂存区的状态
git stash pop [--index] [<stash>]

git stash list执行后可能得到类似下面的结果

1
stash@{0}: WIP on dev: 8297914 m head.htm

之后可以可以通过指定恢复到哪次进度git stash pop stash@{0}

其它命令:git stash apply [–index] [<stash>]除了不删除恢复的进度之外,其余和git stash pop命令一样。 git stash drop [<stash>]删除一个存储的进度。缺省删除最新的进度。git stash clear删除所有存储的进度。git stash branch <branchname> <stash> 基于进度创建分支。

stash 引用位于.git/refs/stash,它指向一个提交对象。git log --graph --pretty=raw stash@{0} -3可以查看提交记录。最新的提交说明中有WIP字样(是Work In Progess的简称),代表了工作区进度。而最新提交的第二个父提交有index on master字样,这个提交代表着暂存区的进度。git diff stash@{0}^2 stash@{0}可以比较原暂存区和原工作区的差异。

查看工作目录状态

1
git status

查看文件改动

git diff

  • 比较工作目录和暂存区 git diff
  • 比较暂存区和版本库 git diff --cached 高版本1.6.1+还可以使用git diff --staged
  • 比较工作目录和版本库 git diff HEAD

查看提交信息

常用命令 git log,快速查看历史记录 git log --pretty=oneline。记录比较多时,可以使用jk等vi的命令向下向上查看。还有个命令可以查看提交信息git whatchanged

git log 的常用选项

选项 说明
-p 按补丁格式显示每个更新之间的代码差异。审查代码查看每次提交带来的变化时很有用
--stat 显示每次更新的文件修改统计信息,如果想了解提交时哪些文件被改变了很有用。显示每个提交哪些文件被改了,-p会显示具体哪些代码发生了变化
--shortstat 只显示 –stat 中最后的行数修改添加移除统计。
--name-only 仅在提交信息后显示已修改的文件清单。
--name-status 显示新增、修改、删除的文件清单。
--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
--graph 显示 ASCII 图形表示的分支合并历史。
--pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
--oneline 在每行显示一个提交的hash和message

限制 git log 输出的选项

选项 说明
-(n) 仅显示最近的 n 条提交 git log -2 查看近两次提交
--since, --after 仅显示指定时间之后的提交。
--until, --before 仅显示指定时间之前的提交。
--author=<name> 仅显示指定作者相关的提交。
--committer=<name> 仅显示指定提交者相关的提交。作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。eg. stackoverflow-1 stackoverflow-2
--grep 仅显示含指定关键字的提交
-S 仅显示添加或移除了某个关键字的提交 git log -S"some code" 显示提交的文件中添加或删除了关键字”some code”的提交

如果要得到同时满足这两个选项搜索条件的提交,就必须用 --all-match 选项

按范围查看提交信息

1
2
git log --since="5 hours"  # 查看5小时以内的提交
git log --before="5 hours" # 查看5小时之前的提交

可以接受的的时间格式:3 hours, 5 minutes, 1 minute, 2013-4-13, 2013.4.13, 2013-4.13

1
2
3
4
5
git log *.xml               # 显示项目路径下的所有以.xml结尾的文件的提交
git log a8b8a42 # 查看提交名称为a8b8a42及其之前的提交,至少应该有四位
git log 8d0a22b..a8b8a42 # 查看提交名称在8d0a22b和a8b8a42之间的提交,不包括起点,只包括终点
git log 8d0a22b..HEAD # 查看提交名称8d0a22b到当前分支末梢的最新版本,也可以不输HEAD
git log --pretty=format:"%h %s" 1.0..HEAD # 查看标签1.0到当前分支末梢的最新版本

git log --pretty=format 常用的选项

选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 –date= 选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明
  • ^:一个脱字符作用相遇回溯一个版本 8d0a22b^是指8d0a22b之前的那个版本
  • ~n:波浪线加数字是指回溯n个版本。8d0a22b~1是指8d0a22b的父节点

以下命令执行结果相同

1
2
3
4
git log -1 HEAD^^^
git log -1 HEAD^~2
git log -1 HEAD~1^^
git log -1 HEAD~3

查看版本之间的差异

1
2
git diff 8d0a22b      # 默认第二个参数不写的话就是HEAD
git diff --stat 1.0 # --stat可以统计改动的代码量,同样第二个参数不写就是HEAD

查看代码块的历史信息

1
git blame index.html

输出:

1
2
3
4
5
^f6bb33f (Feng Hao 2016-05-06 15:46:05 +0800 1) <html>
016f836c (Feng Hao 2016-05-06 15:47:27 +0800 2) <head></head>
24207403 (Feng Hao 2016-05-06 16:23:10 +0800 3) <body>
24207403 (Feng Hao 2016-05-06 16:23:10 +0800 4) </body>
^f6bb33f (Feng Hao 2016-05-06 15:46:05 +0800 5) </html>

有脱字符的表示版本库中的第一个提交

已经提交了但是还是出现00000000 (Not Committed Yet ....) 这个问题可以参考Git blame showing no history一种办法是指明分支git blame <branch> <filename> 或者最好git blame HEAD <filename>;还有中办法是指明-w参数git blame -w <filename>

可以限定查看范围

1
2
3
4
5
git blame -L 12,13 index.html
git blame -L 12,+2 index.html
git blame -L 12,-2 index.html
git blame -L "/<body>*/",+2 index.html
git blame -L "/<body>*/",-2 016f836c^ -- index.html # 路径是最后一个选项,两个短划线(--)隔开之前的选项和后面限定的路径名。

跟踪内容

创建文件original.txt写入三行,并再复制这三行

1
2
git blame original.txt
git blame -M original.txt

后者显示的所有提交名称相同,因为git检测到了重复的内容

复制文件original.txt并重命名为copy.txt

1
git blame -C -C copy.txt

显示出事提交名称和初始文件名original.txt

1
git log -C -C -1 -p		# 可以检测到复制信息

撤销操作

通过git reset HEAD <filename>命令可以撤销已暂存的文件

git checkout -- <filename>命令可以撤销已修改的文件,这是很危险的,意味着对文件做的任何修改都会丢失。--是为了避免文件和引用或提交ID发生冲突。

在 Git 中任何已提交的东西几乎总是可以恢复的。然而,任何未提交的东西丢失后很可能再也找不到了。

reset 命令会以特定的顺序重写三棵树(版本库HEAD、暂存区index、工作区Workdir),在你指定以下选项时停止:

  1. 移动 HEAD 指向的分支引用 (若指定了 –soft,则到此停止)
  2. 使索引看起来像 HEAD (若指定了 –mixed,默认不加参数就是mixed,则到此停止)
  3. 使工作目录看起来像索引 (若指定了 –hard,则到此停止)

下面的速查表列出了命令对树的影响。 “HEAD” 一列中的 “REF” 表示该命令移动了 HEAD 指向的分支引用(如HEAD 当前指向master,则改变master的引用的提交对象),而“HEAD” 则表示只移动了 HEAD 自身(改变HEAD指向的分支)。 特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命令之前请考虑一下。

Command HEAD Index Workdir WD Safe?
Commit Level
reset –soft [commit] REF NO NO YES
reset [commit] REF YES NO YES
reset –hard [commit] REF YES YES NO
checkout [commit] HEAD YES YES YES
File Level
reset (commit) [file] NO YES NO YES
checkout (commit) [file] NO YES YES NO

反转提交

1
2
3
git revert -n HEAD     # -n选项git 会暂存所有变更,而不立即提交,--no-edit不打开编辑器
git revert -n 49005c1
git commit

复位

1
2
git reset --hard HEAD^		# 复位到HEAD的父节点
git reset --hard HEAD~3 # 复位到HEAD的曾祖父节点

revert vs. reset –hard

  • revert相对安全,是对某一次提交undo,并且保留提交历史。
  • reset相对比较危险,要对某一次提交undo则其后的所有提交都会undo,不保留历史,永久性的。

克隆远程版本库

区别于 Subversion 之类的工具,这里是clone而不是checkout。克隆出的是版本库的几乎所有数据。

1
git clone git://github.com/tswicegood/mysite-chp6.git

克隆后本地只有主分支,可以通过git branch -a 查看所有分支,其它分支是隐藏的,可以通过git checkout -b experimental origin/experimental创建并切换分支。

版本库同步

版本库同步,Git从远程的分支获取最新的版本到本地有这样2个命令:

1
2
git fetch
git pull <远程版本库名称> <需要拖入的远程版本库的分支名>

fetch vs pull

  1. git fetch:相当于是从远程获取最新版本到本地,不会自动merge

    1
    2
    3
    git fetch origin master
    git log -p master..origin/master
    git merge origin/master

    以上命令的含义:首先从远程的origin的master主分支下载最新的版本到origin/master分支上;然后比较本地的master分支和origin/master分支的差别;最后进行合并

    上述过程其实可以用以下更清晰的方式来进行:

    1
    2
    3
    git fetch origin master:tmp
    git diff tmp
    git merge tmp

    从远程获取最新的版本到本地的tmp分支上;之后再进行比较合并

  2. git pull:相当于是从远程获取最新版本并merge到本地

    1
    git pull origin master

    上述命令其实相当于git fetch 和 git merge。在实际使用中,git fetch更安全一些,因为在merge前,我们可以查看更新情况,然后再决定是否合并。如果加上--rebase选项相当于git fetchgit rebase

给远程版本库起别名

1
git remote add origin git://github.com/tswicegood/mysite.git

推入远程版本库

1
git push origin master

查看本地创建的全部远程版本库别名

1
git remote

查看某个远程版本库 的详细信息

1
git remote show <版本库别名>

重命名远程仓库简写名

1
git remote rename <old> <new>

删除版本库别名

1
git remote rm <版本库别名>

删除远程分支

1
2
3
git push <remote> :<branch> # since Git v1.5.0
# 或者
git push <remote> --delete <branch> # since Git v1.7.0

分支

Git 中分支是对提交对象的引用,是一个包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件。本地分支在.git/refs/heads目录下,远程分支在.git/refs/remotes目录下。而 HEAD (位于.git/HEAD)指针指向了当前分支。

远程分支是远程仓库分支状态的引用。它们是你不能移动的本地引用,当你做任何网络通信操作时(比如fetch、push等),它们会自动移动。它们以 (remote)/(branch) 形式命名。

上次跟远程仓库通信后a分支又有了新的提交,本地分支将领先远程分支一个提交。只要不与 origin 服务器连接,origin/a 指针就不会移动。

1
2
3
4
5
6
7
8
               +--------+  +--------+
|origin/a| | a |
+--------+ +--------+
| |
v v
+----+ +----+ +----+ +----+
| C0 |<--+ C1 |<--+ C2 |<--+ C3 |
+----+ +----+ +----+ +----+

注意上面的数据是最后一次从服务器上抓取的数据,可能其它人向远程仓库提交了新的内容,git fetch origin(这个命令将远程仓库数据同步到本地,将origin/a指针指向远程仓库分支a最新的一次提交,并不会改变本地分支a,也不会修改工作目录中的内容)后,将会发现从C2处远程分支和本地分支分叉了,需要手动将origin/a合并到a分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                           +--------+
|origin/a|
+--+-----+
|
v
+----+ +----+ +----+ +--+-+
| C0 |<--+ C1 |<--+ C2 |<--+ C4 |
+----+ +----+ +--+-+ +--+-+
^
| +----+
+----+ C3 |
+----+
^
+---+---+
| a |
+-------+

从一个远程追踪分支检出一个本地分支会自动创建一个叫做 “追踪分支”(有时候也叫做 “上游分支”)。 追踪分支是与远程分支有直接关系的本地分支。

如果本地分支与远程分支存在追踪关系,git pullgit push就可以省略远程分支名。

1
2
git pull origin
git push origin

如果本地分支只有一个追踪分支,那么远程仓库名也可以省略。

1
2
git pull
git push

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。下面几种情况能建立追踪关系。

1
2
3
4
5
git clone <url>
git branch <branch> <remote>/<branch>
git checkout -b <branch> <remote>/<branch>
git push -u <remote> <branch>
git branch (--set-upstream-to=<upstream> | -u <upstream>)

当设置好跟踪分支后,可以通过 @{upstream}@{u} 快捷方式来引用它。

1
2
git merge origin/a # 和下面等效
git merge @{u}

查看追踪关系

1
git branch -vv

列出的信息中还包括本地分支领先(ahead)或者落后(behind)远程分支几个提交

创建分支

显示分支

1
2
3
git branch -r # 列出所有远程分支
git branch -a # 列出所有本地分支和远程分支
git branch -v # 列出所有本地分支和最后一次提交

新建分支

1
2
3
git branch RB_1.0         # RB_1.0新分支名,默认指向当前分支的提交对象
git branch RB_1.0 master # RB_1.0新分支名,master 主干
git branch RB_1.0.1 1.0 # 1.0是打过的标签

以下情况创建分支

  • 试验性修改
  • 增加新功能
  • bug修复

切换分支

1
git checkout RB_1.0

创建分支并检出该分支

1
git chechout -b new master  # new是新分支名,master是告诉git,不是从当前分支,而是从主分支上创建新分支,即基于主分支末梢创建新分支

重命名分支

1
git branch -m master mymaster  # master是就分支名,mymaster是新分支名

新分支名和已有的分支名相同时,重命名不会成功。

1
git branch -M master mymaster

可以强制重命名,并覆盖已有分支名称,需小心使用

合并分支

直接合并

1
2
git chechout master
git merge alternate # 将分支alternate的修改合并进master

压合合并

1
2
3
git chechout master
git merge --squash contact
git commit -m "add contact file" # 将分支contact多次提交的修改合并为一次提交

拣选合并

拣选一次提交进行合并

1
2
git chechout master
git cherry-pick 3d5a506

拣选多个提交

1
2
3
git checkout master
git cherry-pick -n 3d5a506 # 3d5a506是提交名称
git commit # 不需要-m参数

冲突处理

如果在不同分支上对同一文件的同一文本块进行不同的修改,合并这两个不同分支时会有冲突

1
git mergetool

选择工具来人工处理冲突

1
git commit     # 不需要-m参数

把RB_1.0分支上所做的修改合并到主分支上,通过rebase合并,提交历史会更加简洁

1
2
git chechout master
git rebase RB_1.0

删除分支

1
git branch -d RB_1.0 	# 仅仅是删除了分知名,该分支的内容并没有被删除

如果分支还没有合并回当前分支,删除不会成功

1
git branch -D RB_1.0

可以强制删除分支

一般来说运行git branch --merged 结果中前面没有* 的分支都是可以被删除的分支,因为它已经被合并到当前的分支中了。

变基

改写历史记录

重新排序

1
git rebase -i HEAD~3   # 之后在打开的编辑器中改变顺序

将多个提交压合成一个提交

1
git rebase -i 0bb3dfb^  # 将要修改要合并的另一个记录移动到0bb3dfb后,并将其前面的pick改为squash

将一个提交分解成多个提交

1
git rebase -i HEAD~4  # 在打开的编辑器中将要分解的记录前的pick改为edit,保存退出,git reset HEAD^,git diff,对文件修改并分别提交,git rebase --continue

打标签

查看所有标签

1
git tag

Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated)。

一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用。

然而,附注标签是存储在 Git 数据库中的一个完整对象。

轻量标签

1
2
git tag 1.0  # 指向当前提交
git tag 1.0 RB_1.0 # 指向特定提交

附注标签

1
git tag -a 1.0 -m 'version 1.0'

通常建议创建附注标签,这样你可以拥有更多标签信息

后期打标签

1
git tag -a 1.0 9fceb02

推送标签

1
2
3
git push origin 1.0
# 推送所有标签
git push origin --tags

删除标签

1
git tag -d 1.0

检出标签

1
git checkout -b version1 1.0

创建归档文件

1
2
git archive --format=tar --prefix=mysite-1.0/ 1.0 | gzip > mysite-1.0.tar.gz
git archive --format=zip --prefix=mysite-1.0/ 1.0 > mysite-1.0.zip

文件重命名和移动

1
git mv index.html hello.html

合并其他remote上面的更新

在 Github 上面 Fork 别人的项目时,我们常常会遇到主项目有了更新,这个时候怎么把主项目的更新合并到自己 Fork 的版本里面来呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 先把 tualatrix Fork 的版本获取到本地
~/work $ git clone git://github.com/tualatrix/ruby-china.git
~/work $ cd ruby-china
~/work/ruby-china <master> $ git remote
origin
# 添加 huacnlee (也就是主项目的 remote 地址)
~/work/ruby-china <master> $ git remote add huacnlee git://github.com/huacnlee/ruby-china.git
# 用 fetch 命令获取 huacnlee 的所有分支
~/work/ruby-china <master> $ git fetch huacnlee
remote: Counting objects: 499, done.
remote: Compressing objects: 100% (143/143), done.
remote: Total 315 (delta 211), reused 253 (delta 172)
Receiving objects: 100% (315/315), 190.17 KiB | 92 KiB/s, done.
Resolving deltas: 100% (211/211), completed with 72 local objects.
From git://github.com/huacnlee/ruby-china
* [new branch] master -> huacnlee/master
# 将 huacnlee 的 master 分支的改动合并过来,目前是处与 master 分支
~/work/ruby-china <master> $ git merge huacnlee/master

Git的资源:免费的编程中文书籍索引–版本控制
Git 工作流

Updated: