简陋的Git教程(也算是学习Git的个人总结吧)

最后更新于:2022-04-01 19:53:56

覆盖了Git中很基础的一部分。命令行中输入的代码用深蓝色表示,输出的代码用棕色表示 首先安装Git: ~~~ $ sudo apt-get install git $ sudo apt-get install git-doc git-svn git-email git-gui gitk ~~~ 首先要设置好用户的姓名和邮箱(为保证提交时提交者和作者信息的正确性): ~~~ $ git config --global user.name “xx” $ git config --global user.email “xx@xx.com” ~~~ 然后进入工作目录gitTest ~~~ $ cd gitTest $ git init ~~~ 创建版本库,这时会发现工作目录文件夹下多了一个.git的目录 创建一个文件test.txt,内容为hello world ~~~ $ git add test.txt 提交到版本库之前需要进行的行为 $ git commit -m “test1” 提交到版本库,提交说明为test1 [master(root-commit) 1e96add] test1 1files changed, 1 insertions(+), 0 deletions(-) create mode 100644 test.txt ~~~ 修改文件test.txt,添加一行hello ~~~ $ git diff可以看到修改后的文件与版本库中的文件的差异 diff --git a/test.txt b/test.txt index 3b18e51..48d1c4e 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,2 @@ hello world +hello $ git status -s 查看文件状态,-s表示查看精简状态 M test.txt 注意:M前面有个空格,M处在第二个位置上 ~~~ 如果要提交修改的话: ~~~ git commit -m "add new line" # On branch master # Changes not staged for commit: #   (use "git add ..." to update what will be committed) #   (use "git checkout -- ..." to discard changes in working directory) # #modified:   test.txt # no changes added to commit (use "git add" and/or "git commit -a") ~~~ 说明修改后不能直接提交,要先对文件执行git add命令,将修改的文件添加到暂存区,然后才能提交 ~~~ $ git add test.txt 这时$ git diff看不到差异 $ git diff HEAD 或 $ git diff master 会发现有差异,HEAD为当前版本库的头指针,master分支为当前的工作分支 diff --git a/test.txt b/test.txt index 3b18e51..48d1c4e 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,2 @@ hello world +hello $ git status -s M  test.txt这时M前面没有空格,M处于第一个位置上 ~~~ 第一个位置代表版本库中的文件与处于中间状态(提交暂存区)中的文件相比有改动;第二个位置代表工作区当前的文件与处于中间状态(提交暂存区)中的文件相比有改动。 若在test.txt中再添加一行world ~~~ $ git status -s MM test.txt $ git diff 工作区与暂存区的差异 diff --git a/test.txt b/test.txt index 48d1c4e..ecd874d 100644 --- a/test.txt +++ b/test.txt @@ -1,2 +1,3 @@ hello world hello +world $ git diff master、$ git diff HEAD 工作区和版本库的差异 diff --git a/test.txt b/test.txt index 3b18e51..ecd874d 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,3 @@ hello world +hello +world $ git diff --cached 暂存区和版本库的差异 diff --git a/test.txt b/test.txt index 3b18e51..48d1c4e 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,2 @@ hello world +hello ~~~ 这时如果直接提交的话,提交上去的test.txt只有两行,因为后面添加的world一行还未添加到暂存区 ~~~ $ git commit -m "2 lines"  [master 584d55d] 2 lines 1 files changed, 1 insertions(+), 0 deletions(-) $ git add test.txt $ git commit -m "3 lines" 成功提交三行的test.txt文件 [master 2b6b6e3] 3 lines 1 files changed, 1 insertions(+), 0 deletions(-) 修改test.txt,添加一行abc $ git checkout -- test.txt ~~~ 会发现新添加的行abc不见了,checkout会撤销工作区中test.txt文件尚未提交的修改,若添加到暂存区,则不能撤销。相当于取消自上次执行add以来的本地修改 ~~~ $ git branch 可以查看分支 * master *号表示当前分支 $ git log 查看日志 commit ec615c08858af2e90de2fe7ba1911a00011360d0 Author: xx Date:   Fri Aug 10 15:56:11 2012 +0800   3 lines commit 584d55d6eee909a33130a8528326e2998dc82450 Author: xx Date:   Fri Aug 10 15:53:46 2012 +0800   2 lines commit 1e96add73eef18106747d7127f3279ff950f262d Author: xx Date:   Fri Aug 10 13:23:36 2012 +0800   test1 ~~~ 一长串字母和数字的组合是提交ID,由40个十六进制的数字组成,是SHA1哈希值。这种方法使得发生冲突的几率变得很小。 ~~~ $ git rev-parse HEAD 显示引用对应的提交ID ec615c08858af2e90de2fe7ba1911a00011360d0 $ git rev-parse HEAD^HEAD的父提交 584d55d6eee909a33130a8528326e2998dc82450 ~~~ 语法:master代表分支master中最新的提交;HEAD代表版本库中最近的一次提交;^指代父提交;^2代表第二个父提交;~可以指代祖先提交(父亲的父亲的...);ID^{tree}访问提交对应的树对象;ID:path/to/file访问某一次提交对应的文件对象;:path/to/file访问暂存区中的文件对象 $ git log --pretty=raw --graph通过提交对象之间的相互关联,可以识别出一条跟踪链,可以通过--graph参数看到 ~~~ * commit ec615c08858af2e90de2fe7ba1911a00011360d0 | tree abb0df6cfbbab361ce0002af3fd29c0d7237fa9c | parent 584d55d6eee909a33130a8528326e2998dc82450 | author xx 1344585371 +0800 | committer xx 1344585607 +0800 |  |     3 lines |   * commit 584d55d6eee909a33130a8528326e2998dc82450 | tree c1566beea63b0cc08e940916b5ae835d4ef86b92 | parent 1e96add73eef18106747d7127f3279ff950f262d | author xx 1344585226 +0800 | committer xx 1344585226 +0800 |  |     2 lines |   * commit 1e96add73eef18106747d7127f3279ff950f262d tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d author xx 1344576216 +0800 committer xx 1344576216 +0800     test1 ~~~ $ git cat-file ec615c查看此提交ID(在不冲突的前提下取至少4位)对应的信息,需要加参数 -t类型 -s大小 -p详细信息 在原来基础上再添加abc一行,add并commit,提交说明设为"4 lines" 由日志可以看到提交ID为12e76d61f5c66af0a6c9e6302b2362bb36203bb1 $ git reset --hard HEAD^ 人为修改游标,将master重置到上一个老的提交上(--hard参数会替换引用指向、暂存区、工作区;--soft替换引用指向;--mixed即默认替换引用的指向、暂存区) ~~~ HEAD is now at ec615c0 3 lines $ cat test.txt hello world hello world ~~~ 重置让提交历史也改变了 ~~~ $ git log commit ec615c08858af2e90de2fe7ba1911a00011360d0 Author: xx Date:   Fri Aug 10 15:56:11 2012 +0800   3 lines commit 584d55d6eee909a33130a8528326e2998dc82450 Author: xx Date:   Fri Aug 10 15:53:46 2012 +0800   2 lines commit 1e96add73eef18106747d7127f3279ff950f262d Author: xx Date:   Fri Aug 10 13:23:36 2012 +0800   test1 ~~~ 挽救错误的重置需要用reflog。 ~~~ $ tail -5 .git/logs/refs/heads/master这个文件记录了master分支指向的变迁,最新的改变追加到文件的末尾。 1e96add73eef18106747d7127f3279ff950f262d 584d55d6eee909a33130a8528326e2998dc82450 xx 1344585226 +0800commit: 3 lines 584d55d6eee909a33130a8528326e2998dc82450 2b6b6e3554ddf8b79759294403b9c31c88bcf986 xx 1344585371 +0800commit: 2 lines 2b6b6e3554ddf8b79759294403b9c31c88bcf986 ec615c08858af2e90de2fe7ba1911a00011360d0 xx 1344585607 +0800commit (amend): 3 lines ec615c08858af2e90de2fe7ba1911a00011360d0 12e76d61f5c66af0a6c9e6302b2362bb36203bb1 xx 1344594757 +0800commit: 4 lines 12e76d61f5c66af0a6c9e6302b2362bb36203bb1 ec615c08858af2e90de2fe7ba1911a00011360d0 xx 1344595145 +0800HEAD^: updating HEAD $ git reflog show master | head -6  ec615c0 master@{0}: HEAD^: updating HEAD 12e76d6 master@{1}: commit: 4 lines ec615c0 master@{2}: commit (amend): 3 lines 这个amend是当时不小心用了一个commit的--amend参数,现在暂时忽略这个amend 2b6b6e3 master@{3}: commit: 3 lines 584d55d master@{4}: commit: 2 lines 1e96add master@{5}: commit (initial): test1 ~~~ 从这里可以看出消失的提交说明为"4 lines"的提交又出现了 ~~~ $ git reset --hard master@{1} HEAD is now at 12e76d6 4 lines $ cat test.txt hello world hello world abc ~~~ 发现abc一行又回来了,提交历史也回来了,恢复master的操作页记录在了日志中。 ~~~ $ git reflog show master | head -7 12e76d6 master@{0}: master@{1}: updating HEAD ec615c0 master@{1}: HEAD^: updating HEAD 12e76d6 master@{2}: commit: 4 lines ec615c0 master@{3}: commit (amend): 3 lines 2b6b6e3 master@{4}: commit: 3 lines 584d55d master@{5}: commit: 2 lines 1e96add master@{6}: commit (initial): test1 ~~~ 如果再向test.txt中添加一行abcd $ git add test.txt但不commit $ git reset HEAD test.txt相当于取消之前执行的git add命令,是用指定提交状态(HEAD)下的文件test.txt替换掉暂存区中的文件 $ git diff会发现工作区和暂存区内容不一样了,暂存区的内容和版本库统一了 ~~~ diff --git a/test.txt b/test.txt index e0593e8..d76f881 100644 --- a/test.txt +++ b/test.txt @@ -2,3 +2,4 @@ hello world hello world abc +abcd $ git reset --hard HEAD ~~~ 统一工作区、暂存区、版本库的内容,为下一步做准备 ~~~ $ cat .git/HEAD查看当前HEAD的指向,指向master ref: refs/heads/master $ git branch -v * master 12e76d6 4 lines $ git checkout 12e76d6^检出该ID的父提交 Note: checking out '12e76d6^'. ~~~ 分离头指针状态,指的是头指针指向了一个具体的提交ID,而不是一个引用(分支) ~~~ You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at ec615c0... 3 lines ~~~ $ git reflog可以看到HEAD的指针被更改了 ~~~ ec615c0 HEAD@{0}: checkout: moving from master to 12e76d6^ 12e76d6 HEAD@{1}: master@{1}: updating HEAD ec615c0 HEAD@{2}: HEAD^: updating HEAD 12e76d6 HEAD@{3}: commit: 4 lines ec615c0 HEAD@{4}: commit (amend): 3 lines 2b6b6e3 HEAD@{5}: commit: 3 lines 584d55d HEAD@{6}: commit: 2 lines 1e96add HEAD@{7}: commit (initial): test1 $ git rev-parse HEAD master发现HEAD和master的指向不一样了 ec615c08858af2e90de2fe7ba1911a00011360d0 12e76d61f5c66af0a6c9e6302b2362bb36203bb1 ~~~ $ git branch现在不处于任何分支上 * (no branch) master 这时若修改文件并提交,切换到master分支上以后,改动会消失,此提交虽然暂时在版本库中,但没被任何分支跟踪到,因此不能保证这个提交会永久存在。在文件中添加一行no branch ~~~ $ git add test.txt $ git commit -m "no branch" [detached HEAD 6014965] no branch 1 files changed, 1 insertions(+), 0 deletions(-) ~~~ $ git checkout master切换到master分支上,发现改动消失了。若为git checkout branch -- filename 则维持HEAD指向不变,用branch所指向的提交中的filename替换暂存区和工作区中相应的文件;git checkout -- .或git checkout .会取消所有本地相对于暂存区的修改。 ~~~ $ git merge 6014965合并到当前分支 Auto-merging test.txt CONFLICT (content): Merge conflict in test.txt Automatic merge failed; fix conflicts and then commit the result. $ cat test.txt hello world hello world HEAD abc ======= no branch 6014965 ~~~ 需要修改文件以解决冲突并add、commit,才能合并 ~~~ $ git log --graph --pretty=oneline 会发现出现了不一样的分支 *   29744ad4a7688770729de3c7c7cf00895025a89e to merge |\   | * 60149653b9d2488e423782a7ae5cdf710e1cd5cb no branch * | 12e76d61f5c66af0a6c9e6302b2362bb36203bb1 4 lines |/   * ec615c08858af2e90de2fe7ba1911a00011360d0 3 lines * 584d55d6eee909a33130a8528326e2998dc82450 2 lines * 1e96add73eef18106747d7127f3279ff950f262d test1 ~~~ 最新提交会有两个父提交 新建test2.txt文件并add、commit,用于下文要介绍的删除命令。 若直接在工作区中删除文件,则对暂存区和版本库均不起作用。 $ git rm test2.txt 则此文件在工作区和暂存区被删除,更改名字也类似,git mv命令 ~~~ $ git commit -m "use git rm to delete file" [master 756e9d5] use git rm to delete file 0 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test2.txt ~~~ 新建test3.txt文件并add、commit,用于另一个删除方法。 首先在本地删除test3.txt ~~~ $ git status -s D test3.txt ~~~ $ git add -u将本地有改动(包括修改和删除)的文件标记到暂存区,-A则是把工作区的所有改动及新增文件添加到暂存区。-i提供交互式界面,可以进行选择性添加 ~~~ $ git status -s D  test3.txt $ git commit -m "delete file test3.txt" [master 8ce4b0e] delete file test3.txt 0 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test3.txt ~~~
';

(十二) 前几章的补充

最后更新于:2022-04-01 19:53:53

$ git fsck 查看版本库中包含的没有被任何引用关联的松散对象 $ git prune 清理版本库(实际操作中很少用到) 若撤销的操作仍然记录在reflog中,则Git认为撤销的提交和大文件都可以被追踪到,还在使用着,所以无法用git prune命令删除。如果确认真的要丢弃不想要的对象,需要对版本库的reflog做过期操作,相当于将.git/logs/下的文件清空。reflog的过期操作默认只会让90天前的数据过期。需要为git reflog命令提供--expire=参数,强制让之前的记录全部过期。使用now作为时间参数,让reflog的全部记录都过期。$ git reflog expire --expire=now all $ git pack-refs --all --prune 若未将配置gc.packrefs关闭,则对分散在.git/refs下的文件进行打包,打包到文件.git/packed-refs中 $ git reflog expire --all 清空reflog中90天前的记录。 $ git repack 对松散对象进行打包,凡是有引用关联的对象都被打在包里,未被关联的对象仍旧以松散对象的形式保存。 可以向git gc提供--prune=参数,其中的时间参数传递给git prune --expire=,实现对指定日期之前的未被关联的松散对象进行清理。 $ git rerere gc 对合并冲突的历史记录进行过期操作 对于1.6.6及以后版本的Git基本上不需要手动执行git gc命令了,部分Git命令会自动执行git gc --auto命令,如下: 执行git merge进行合并操作后;执行git receive-pack,即版本库接受其他版本库PUSH来的提交后;执行git rebase -i进行交互式变基操作后;执行git am对mbox邮箱中通过邮件提交的补丁在版本库中进行应用的操作后。 综上所述,对于提供共享式“写操作”的Git版本库,可以免维护。所谓的共享式写操作,就是版本库作为一个裸版本库放在服务器上,团队成员可以通过PUSH操作将提交推送到共享的裸版本中。每一次推送操作都会触发git gc --auto命令,对版本库进行按需整理。 对于非独立工作的本地工作区,也可以免维护。因为和他人协同工作的本地工作区会经常执行git pull操作从他人版本库或从共享的版本库拉回新提交,执行git pull操作会触发git merge操作,因此也会对本地版本库进行按需整理。 整理的太勤则没有必要,还会增加系统负担;疏于整理则会导致积累太多的松散文件,当真正开始版本库整理的时候会占用过多的系统资源,影响用户体验。因此实际操作中只有在特定的条件下才会触发真正的版本库整理。主要的触发条件是:松散对象只有超过一定的数量时才会执行。可以通过设置配置变量gc.auto的值调整频率,不过不能设置为0,否则git gc --auto命令永远不会触发版本库的整理。
';

(十一) Git克隆

最后更新于:2022-04-01 19:53:51

用法1:git clone 指向的版本库创建一个克隆到目录。目录相当于克隆版本库的工作区,文件都会检出,版本库位于工作区的.git目录中 用法2:git clone --bare 用法3:git clone --mirror 用法2和用法3创建的克隆版本库都不包含工作区,直接就是版本库的内容,这样的版本库称为裸版本库。一般约定俗成裸版本库的目录名以.git做后缀,所以上面示例中将克隆出来的裸版本库目录名写作。区别在于用法3克隆出来的裸版本对上游版本库进行了注册,这样可以在裸版本库中使用git fetch命令和上游版本库进行持续同步。 不使用--bare或--mirror创建出来的克隆包含工作区,这样就会产生两个包含工作区的版本库,这两个版本库对等。这两个工作区本质上没有区别,往往提交在一个版本A中进行,另一个B作为备份。只能从B执行git pull命令从A中拉回新的提交实现版本库同步,而不能从版本库A向版本库B执行git push推送操作 还可以通过git init的方式创建裸版本库,需要加--bare参数。 当执行git push命令时,如果没有设定推送的分支,而且当前分支也没有注册到远程的某个分支,将检查远程分支是否有和本地相同的分支名(如master),如果有,则推送,否则报错。
';

(十) 改变历史

最后更新于:2022-04-01 19:53:49

#### 拣选指令:git cherry-pick 从众多的提交中挑选出一个提交应用在当前的工作分支中。需要一个提交ID作参数,操作过程相当于将该提交导出为补丁文件,然后在当前HEAD上重放,形成无论内容还是提交说明都一致的提交。 #### 对提交执行变基操作:git rebase 实现将指定范围的提交嫁接到另外一个提交之上。 -i 交互式界面 --continue变基遇到冲突而暂停的情况下,先完成冲突解决(添加到暂存区,不提交),然后在恢复变基操作时使用 --skip变基遇到冲突而暂停的情况下,跳过当前提交时使用 --abort变基遇到冲突而暂停的情况下,终止变基操作,回到之前的分支时使用 $ git rebase --onto 首先会执行 git checkout 切换到  。 因为会切换到  ,因此如果  指向的不是一个分支(如 master),则变基操作是在 detached HEAD (分离头指针)状态进行的,当变基结束后,还要像在“时间旅行一”中那样,对 master 分支执行重置以实现把变基结果记录在分支中。 将 .. 所标识的提交范围写到一个临时文件中。 还记得前面介绍的版本范围语法, .. 是指包括  的所有历史提交排除  以及  的历史提交后形成的版本范围。 当前分支强制重置(git reset --hard)到  。 相当于执行: git reset --hard  。 从保存在临时文件中的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上。 如果遇到提交已经在分支中包含,跳过该提交。 如果在提交过程遇到冲突,变基过程暂停。用户解决冲突后,执行 git rebase --continue 继续变基操作。或者执行 git rebase --skip 跳过此提交。或者执行 git rebase --abort 就此终止变基操作切换到变基前的分支上。 丢弃历史 由里程碑A对应的提交构造出一个根提交至少有两种方法。 第一种:使用git commit-tree命令。用A^{tree}语法访问里程碑A对应的目录树,使用git commit-tree命令直接从该目录树创建提交。 ~~~ $ git cat-file -p A^{tree} $ echo "Commit from tree of tag A." | git commit-tree A^{tree} ~~~ 第二种:使用git hash-object命令。用A^0语法访问里程碑A对应的提交。将输出过滤掉以parent开头的行,并将结果保存到一个文件中。运行git hash-object命令,将文件tmpfile作为一个commit对象写入对象库。 ~~~ $ git cat-file commit A^0 $ git cat-file commit A^0 | sed -e '/^parent/ d' > tmpfile $ git hash-object -t commit -w -- tmpfile ~~~ 然后就可以使用变基操作。 #### 反转提交:git revert 重新做一次新的提交,相当于用错误的历史提交的反向提交,来修正错误的历史提交。
';

(九) 历史穿梭

最后更新于:2022-04-01 19:53:47

#### 版本表示法:git rev-parse --git-dir显示Git版本库的位置   --show-cdup显示当前工作区目录的深度  --parseopt解析命令行参数 ~~~ $ git rev-parse --symbolic --branches 显示分支 $ git rev-parse --symbolic --tags 显示里程碑 $ git rev-parse --symbolic --glob=refs/* 显示定义的所有引用。其中refs/remotes/目录下的引用称为远程分支(或远程引用) $ git rev-parse HEAD 显示HEAD对应的SHA1哈希值 $ git rev-parse A^{tree} A: 显示里程碑A对应的目录树 $ git rev-parse A^{tree}:src/Makefile A:src/Makefile 显示树里面的文件 $ git rev-parse :/"commit A" 通过在提交日志中查找字串的方式显示提交 ~~~ #### 版本范围表示法:git rev-list 一个提交ID实际上就可以代表一个版本列表,含义是该版本的所有历史提交 $git rev-list --oneline A 两个或多个版本,相当于每个版本单独使用时指代的列表的并集 在一个版本前面加上符号^含义是取反,即排除这个版本及其历史版本 ^G D等价于G..D ...表示法含义是两个版本共同能访问到的除外 B^@表示某提交的历史提交,自身除外 B^!表示提交本身不包括其历史提交 #### 浏览日志:git log 后面可以接表示版本范围的参数(如上git rev-list) --graph参数可以显示字符界面的提交关系图,而且不同的分支可以用不同的颜色表示 -显示最近的n条日志 -p可以在显示日志的时候同时显示改动 当不需要知道具体的改动而只想知道改动在哪些文件上时可以使用--stat参数 --pretty=raw显示commit的原始数据,可以显示提交对应的树ID --pretty=fuller同时显示作者和提交者,两者可以不同 --pretty=oneline提供最精简的日志输出,也可以使用--oneline参数 如果只想查看和分析某一个提交,也可以使用git cat-file(-p参数的含义是美观的输出)或git show命令 #### 差异比较:git diff ~~~ $ git diff B A 比较里程碑B和里程碑A $ git diff A 比较工作区和里程碑A $ git diff --cached A 比较暂存区和里程碑A $ git diff 比较工作区和暂存区 $ git diff --cached 比较暂存区和HEAD $ git diff HEAD 比较工作区和HEAD $ git diff -- 显示不同版本间该路径下的文件差异 $ git diff 可以在Git版本库之外执行,对非Git目录进行比较 $ git diff --word-diff 逐词比较 ~~~ #### 文件追溯:git blame 在软件开发过程中当发现bug并定位到具体代码时,可以指出何时和什么版本引入的此bug。 当针对文件执行git blame命令时,就会逐行显示文件,在每一行的行首显示此行最早是在什么版本引入的,由谁引入的 如果只想显示某几行,使用-L n,m参数 #### 二分查找:git bisect ~~~ $ git bisect start 开始二分查找 $ git bisect bad A 将A版本标记为坏提交 $git bisect good 将当前版本标记为好提交 $ git checkout bisect/bad 切换到最终定位的坏提交版本 $ git bisect reset 当对bug定位和修复后,撤销二分查找在版本库中遗留的临时文件和引用。撤销二分查找后,版本库切换回执行二分查找之前所在的分支 ~~~ **如果把好提交当成了坏提交:** ~~~ $ git bisect log > logfile 把二分查找的日志保存在一个文件中,编辑这个文件,删除记录了错误动作的行。 $ git bisect reset 结束正在进行的出错的二分查找 $ git bisect replay logfile 通过日志文件恢复进度,重启二分查找 ~~~ **自动化测试:** 支持run子命令,可以运行一个自动化测试脚本,实现自动的二分查找。若脚本退出码为0,正在测试的版本是一个好版本;退出码为125,正在测试的版本被跳过;退出码为1~127(125除外),正在测试的版本是一个坏版本 测试脚本good-or-bad.sh,步骤如下: ~~~ (1)$ git bisect start master G 从已知的坏版本master和好版本G开始新一轮的二分查找 (2)$ git bisect run sh good-or-bad.sh 自动化测试 (3)$ git describe refs/bisect/bad 显示定义到的坏版本 ~~~ #### 获取文件历史版本命令: ~~~ $ git ls-tree 查看历史提交的目录树 $ git checkout 整个工作区切换到历史版本 $ git checkout -- 检出某文件的历史版本 $ git show : > new_name 检出某文件的历史版本到其他文件名 ~~~
';

(八) Git基本操作

最后更新于:2022-04-01 19:53:44

#### 删除文件 直接在工作区删除,对暂存区和版本库没有任何影响。本地删除如果要反映在暂存区中应该用git rm命令,对不想删除的文件执行git checkout -- ,可以让文件在工作区重现。用git rm命令执行删除后,删除动作加入了暂存区,这时执行提交动作就从真正意义上执行了文件删除,不过文件只是在版本库的最新提交中被删除了,在历史提交中尚在。 $ git add -u  将本地有改动(包括修改和删除)的文件标记到暂存区。 #### 恢复删除的文件 ~~~ $ git cat-file -p HEAD^:test.txt > test.txt 从前一提交中恢复test.txt文件 $ git show HEAD^:test.txt > test.txt 效果相同 $ git checkout HEAD^ -- test.txt 也可以 $ git add -A 将工作区的所有改动及新增文件添加到暂存区 ~~~ 然后执行commit操作,则文件被恢复 #### 移动文件(改名操作) 改名操作相当于对旧文件执行删除,对新文件执行添加,git mv可以由git rm和git add两条命令取代 $ git mv oldname newname 完成改名操作 #### 查看版本号 $ git describe 查看当前版本号 git log命令的--decorate参数可以在提交ID的旁边显示该提交关联的引用(里程碑或分支) #### 选择性添加文件 $ git add -i可以进入一个交互式界面操作 文件忽略功能 若把编译的目标文件及其他临时文件加入版本库中,浪费存储空间不说,甚至还会造成冲突。 可以创建一个.gitignore文件,其作用范围是所处目录及其子目录,编辑好要忽略的文件,然后添加到版本库中。如果不希望添加到库里,也不希望.gitignore文件带来干扰,可以在忽略文件中忽略自己。 被忽略以后,只有使用git status命令的--ignore参数,才会在状态显示中看到被忽略的文件。添加时git add -A和git add .都失效,只有在添加操作的命令行中明确写入文件名并提供-f参数才能真正添加。忽略只对未跟踪文件有效,对于已加入版本库的文件无效。 文件.gitignore设置的文件忽略是共享式的。之所以被称为“共享式”,是因为.gitignore被添加到版本库后成为了版本库的一部分,当版本库共享给他人(克隆),或者把版本库推送(PUSH)到集中式的服务器或他人的版本库时,这个忽略文件就会在他人的工作区中同样生效。相对应的是独享式,有两种方式: 第一种是针对具体版本库的,即在版本库.git目录下的一个文件.git/info/exclude来设置文件忽略; 另一种是全局的,即通过Git的配置变量core.excludesfile指定的一个忽略文件,其设置的忽略对所有本地版本库均有效。 如果文件忽略对于所有使用此版本库工作的人都有益,就通过在版本库相应目录下建一个.gitignore文件建立忽略;否则,如果是需要忽略工作区中创建的一个试验目录或试验性的文件,则使用本地忽略。 **忽略语法:** 忽略文件中的空行或以#开始的行会被忽略 可以使用通配符,参见Linux手册:glob(7) 如果名称的最前面是一个路径分割符(/),表明要忽略的文件在此目录下,而非子目录的文件 如果名称的最后面是一个路径分割符(/),表明要忽略的是整个目录,同名文件不忽略,否则同名的文件和目录都忽略 通过在名称的最前面添加一个感叹号(!),代表不忽略 #### 文件归档 git archive命令,可以对任意提交对应的目录树建立归档。 ~~~ $ git archive -o latest.zip HEAD  基于最新提交建立归档文件latest.zip $ git archive -o partial.tar HEAD src doc  只把目录src和doc建立到归档partial.tar中 $ git archive --format=tar --prefix=1.0/ v1.0 | gzip > foo-1.0.tar.gz  基于里程碑v1.0建立归档,并且为归档中的文件添加目录前缀1.0 ~~~ 在建立归档时,如果使用树对象ID进行归档,则使用当前时间作为归档中文件的修改时间,而如果使用提交ID或里程碑等,则使用提交建立的时间作为归档中文件的修改时间。 如果使用tar格式建立归档,并且使用提交ID或里程碑ID,还会把提交ID记录在归档文件的文件头中。记录在文件头中的提交ID可以通过git tar-commit-id命令获取。 如果希望在建立归档时忽略某些文件或目录,可以通过为相应文件或目录建立export-ignore属性加以实现。
';

(七) 恢复进度

最后更新于:2022-04-01 19:53:42

#### 删除本地多余的目录和文件 ~~~ $ git clean -nd ~~~ 查看哪些文件和目录会被删除 ~~~ $ git clean -fd ~~~ 真正开始强制删除多余的目录和文件 #### 保存和恢复工作进度 ~~~ $ git stash ~~~ 保存当前的工作进度,会分别对暂存区和工作区的状态进行保存 ~~~ $ git stash list ~~~ 显示进度列表 ~~~ $ git stash pop [--index] [] ~~~ 如果不使用任何参数,会恢复最新保存的工作进度,并将恢复的工作进度从存储的工作进度列表中清除。如果提供参数(来自list显示的列表),则从该中恢复,恢复完毕也将从进度列表中删除。--index除了恢复工作区的文件外,还尝试恢复暂存区 ~~~ $ git stash [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] []] ~~~ git stash命令的完整版,即如果需要在保存工作进度时使用指定的说明,必须使用如下格式:git stash save "message..." 使用参数--patch会显示工作区和HEAD的差异,通过对差异文件的编辑决定在进度中最终要保存的工作区的内容,通过编辑差异文件可以在进度中排除无关内容。 使用-k或--keep-index参数在保存进度后不会将暂存区重置,默认会将暂存区和工作区强制重置 ~~~ $ git stash apply [--index] [] ~~~ 除了不删除恢复的进度之外,其余与git stash pop一样 ~~~ $ git stash drop [] ~~~ 删除一个存储的进度,默认删除最新的进度 ~~~ $ git stash clear ~~~ 删除所有存储的进度 ~~~ $ git stash branch ~~~ 基于进度创建分支
';

(六) Git检出

最后更新于:2022-04-01 19:53:40

#### 检出命令 此命令会重写工作区 该命令的实质就是修改HEAD本身的指向,不会影响分支游标。当执行此命令时,HEAD头指针被更改了,由指向一个引用(分支)变成了指向一个提交ID,分支(master)的指向没有改变,仍旧指向原有的提交ID “分离头指针”状态:指的是HEAD头指针指向了一个具体的提交ID 用法一:git checkout [-q] [] [--] ...    为避免路径和引用(或提交ID)同名而发生冲突,可以在前用--作为分隔 此用法不会改变HEAD头指针,主要用于指定版本的文件覆盖工作区中对应的文件。若省略,则会用暂存区的文件覆盖工作区的文件,否则用指定提交中的文件覆盖暂存区和工作区中对应的文件。 用法二:git checkout [] 此用法会改变HEAD头指针,之所以后面的参数写作,是因为只有HEAD切换到一个分支才可以对提交进行跟踪,否则仍然会进入“分离头指针”的状态,此状态下提交不能被引用关联到,从而可能丢失。所以此用法最主要的作用就是切换到分支,若省略则相当于对工作区进行状态检查。 用法三:git checkout [-m] [[-b | orphan] ] [] 此用法主要是创建和切换到新的分支,新的分支从指定的提交开始创建。新分支与master分支没什么实质的不同,都是在refs/heads命名空间下的引用。 ~~~ $ git checkout branch ~~~ 检出branch分支,更新HEAD以指向branch分支,以及用branch指向的树更新暂存区和工作区。 ~~~ $ git checkout 、 $ git checkout HEAD ~~~ 汇总显示工作区、暂存区与HEAD的差异 ~~~ $ git checkout -- filename ~~~ 用暂存区中filename文件覆盖工作区中的filename文件,相当于取消自上次执行git add filename以来(如果执行过)的本地修改(会悄无声息覆盖) ~~~ $ git checkout branch -- filename ~~~ 维持HEAD的指向不变,用branch所指向的提交中的filename替换暂存区和工作区中相应的文件,会将暂存区和工作区中的filename文件直接覆盖 ~~~ $ git checkout -- 或 $ git checkout . ~~~ 取消所有本地的修改(相对于暂存区),相当于用暂存区的所有文件直接覆盖本地文件,不给用户任何确认机会。
';

(五) Git重置

最后更新于:2022-04-01 19:53:38

#### 重置命令的用法 用法一:git reset [-q] [] [--] ...   为了避免路径和引用(或提交ID)同名而发生冲突,可以在前用--作为分隔 用法二:git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [] 为可选项,可以使用引用或提交ID,若忽略则相当于使用了HEAD的指向作为提交ID 第一种用法不会重置引用,更不会改变工作区,而是用指定提交状态()下的文件()替换掉暂存区中的文件;第二种用法则会重置引用,根据不同选项对暂存区或工作区进行重置。 动作1:替换引用的指向,引用指向新的提交ID 动作2:替换暂存区,替换后,暂存区的内容和引用指向的目录树一致 动作3:替换工作区,替换后,工作区的内容和暂存区一致,也和HEAD指向的目录树内容相同。 --soft执行1;--mixed执行1、2;--hard执行1、2、3 git reset 仅用HEAD指向的目录树重置暂存区,工作区不受影响,相当于将之前用git add命令更新到暂存区的内容撤出暂存区,引用也未改变,因为引用重置到HEAD相当于没有重置。 git reset -- filename 仅将文件filenmae的改动撤出暂存区,暂存区中其他文件不该变,相当于git add filename的反向操作 #### 挽救错误的重置 通过.git/logs目录下日志文件记录了分支的变更,默认非裸版本库(带有工作区)都提供分支日志功能,因为core.logallrefupdates的值为true Git提供了一个git config命令,对这个文件进行操作。使用show子命令可以显示此文件的内容。查看git reflog的输出和直接查看日志文件最大的不同在于显示顺序的不同,即最新改变放在了最前面显示,而且只显示每次改变的最终的SHA1哈希值。输出还有一个方便的表达式:@{},含义是引用之前第次改变时的SHA1哈希值。 ~~~ $ git reflog show master | head -5 ~~~
';

(四) Git对象

最后更新于:2022-04-01 19:53:35

$ git cat-file -t ID号 查看此ID号对应的对象类型(commit、tree、parent、blob) $ git cat-file -p ID号 查看此ID号对应的对象内容 这些对象保存在Git库中的objects目录下(ID前两位作为目录名,后38位作为文件名) 通过commit对象之间的相互关联,可以很容易地识别出一条跟踪链,这条跟踪链可以在运行git log命令时通过--graph参数看到。使用--pretty=raw参数可以显示每个提交对象的parent属性。跟踪链终结在最后一个提交,它没有parent属性,这实际上是提交的起点。 $ git branch 显示工作分支 当前工作分支在名称前有个*号 #### 访问Git库中的对象 采用部分的SHA1哈希值。不必把40位的哈希值写全,只采用开头的部分(4位以上),只要不与现有的其他哈希值冲突即可。 使用master代表分支master中最新的提交,也可以使用全称refs/heads/master或heads/master。 使用HEAD代表版本库中最近的一次提交。 符号^可以指代父提交,^^则为父提交的父提交。 对于一个提交有多个父提交,可以在符号^的后面用数字表示是第几个父提交。 符号~可以用于指代祖先提交。 提交所对应的树对象,可以用d83u59^{tree}这样的语法访问。 某一次提交对应的文件对象,可以用d83u59:path/to/file这样的语法访问。 暂存区中的文件对象,可以用:path/to/file访问。
';

(三) Git暂存区

最后更新于:2022-04-01 19:53:33

#### 查看工作区文件的改动 ~~~ $ git diff 工作区与提交任务(提交暂存区,stage)中相比的差异 $ git diff HEAD 工作区和HEAD(当前工作分支)相比的差异 $ git diff --cached (或--staged)提交暂存区(提交任务,stage)和版本库中文件的差异 $ git status 显示状态 -s表示用精简方式 ~~~ -b同时显示当前工作分支的名称 其中标识有两列,拿状态M来说 第一列含义是:版本库中的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比有改动 第二列含义是:工作区当前的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比有改动 文件./git/index实际上是一个包含文件索引的目录树,像一个虚拟的工作区。在这个虚拟工作区的目录树中,记录了文件名和文件的状态信息(时间戳和文件长度等)。文件的内容并没有存储在其中,而是保存在Git对象库.git/objects目录中,文件索引建立了文件和对象库中对象实体之间的对应。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570723345e470.png) 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage, index),标记为 "master" 的是 master 分支所代表的目录树。 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个“游标”。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,我们会在后面的章节重点介绍。 当对工作区修改(或新增)的文件执行 "git add" 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID 被记录在暂存区的文件索引中。 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。 当执行 "git reset HEAD" 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。 当执行 "git rm --cached " 命令时,会直接从暂存区删除文件,工作区则不做出改变。 当执行 "git checkout ." 或者 "git checkout -- " 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。 当执行 "git checkout HEAD ." 或者 "git checkout HEAD " 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。 ### 目录树的浏览 $ git ls-tree -l HEAD 查看HEAD(版本库中当前提交)指向的目录树 -l参数可以显示文件大小 输出的文件条目从左至右,第一个字段是文件的属性,第二个字段(blob)说明是Git对象库中的一个blob对象(文件),第三个字段是该文件在对象库中对应的ID——一个40位的SHA1哈希值格式的ID,第四个字段是文件大小,第五个字段是文件名。 在浏览暂存区的目录树之前,首先清除工作区当前的改动。 $ git clean -fd 清除当前工作区中没有加入版本库的文件和目录(非跟踪文件和目录),然后执行$ git checkout .命令,用暂存区内容刷新工作区。 $ git ls-files -s 显示暂存区的目录树,其中第三个字段不是文件大小而是暂存区编号 若想针对暂存区的目录树使用git ls-tree命令,需要先将暂存区的目录树写入Git对象库,然后针对该目录树执行git ls-tree命令 $ git write-tree 输出的就是写入Git对象库的TreeID,这个ID将作为下一条命令的输入 5b873f747ccb268e4491f289eb37fc675ff5825b $ git ls-tree -l 5b873f747 只需写前几位,只要不与其他对象的ID冲突即可 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad      12test 可以看出到处都是40位的SHA1哈希值格式的ID,可以用于指代文件内容(blob)、目录树(tree)和提交。 $ git write-tree | xargs git ls-tree -l -r -t 递归显示目录内容使用-r参数,显示递归过程中遇到的每棵树而不是只显示最终文件使用-t参数 ### 不要使用git commit -a 带上此参数,对本地所有变更的文件执行提交操作,包括对本地修改的文件和删除的文件,但不包括未被版本库跟踪的文件。可以简化一些操作,减少用git add命令标识变更文件的步骤,但是丢掉了对提交内容进行控制的能力。 ### 保存当前工作进度 $ git stash 运行完此命令后再查看工作区状态,会发现工作区尚未提交的改动(包括暂存区的改动)全都不见了
';

(二) Git初始化

最后更新于:2022-04-01 19:53:31

### 查看Git版本 ~~~ $ git --version ~~~ ### 配置Git变量 这些设置会在全局文件(用户主目录下的.gitconfig)或系统文件(如/etc/gitconfig)中做永久记录 #### 配置用户名和邮件地址:(将在版本库提交时用到,为确保提交者信息的正确性一定要设置) ~~~ $ git config --global user.name "your name" $ git config --global user.email "your email address" ~~~ #### 配置Git别名:(以便使用更为简洁的子命令) 下面的命令使别名能被所有用户使用(需要系统管理员权限) ~~~  # git config --system alias.st status         类似的还有ci commit, co checkout, br branch ~~~ 下面的命令只在本用户的全局配置中添加Git命令别名 ~~~  #git config --global alias.st status ~~~ #### 开启颜色显示: ~~~ $ git config --global color.ui true ~~~ ### 初始化Git版本库 首先进入工作目录(设为gitTest),然后$ git init,则创建了隐藏目录.git。也可在git init命令后直接输入目录名称以自动完成工作目录的创建 然后在gitTest文件夹下创建文件test ~~~ $ git add test 将test文件添加到版本库 ~~~ 需要再执行一次提交操作,需要提交说明。当Git提交时,如果不在命令行使用-m参数提供提交说明,Git会自动打开一个编辑器要求输入提交说明 ~~~ $ git commit -m "说明" ~~~ 输出如下内容: ~~~ [master (root-commit) 4ffa0a3] 说明 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 test ~~~ 说明此次提交在master的分支上,并且是该分支的第一个提交(root-commit),提交ID为4ffa0a3 commit的参数: --amend 对刚刚的提交进行修补 --allow-empty 允许空白提交 --reset-author 将Author的ID同步修改,也会重置AuthorDate信息 ### Git的一些其他命令 #### 搜索版本库中的文件内容 ~~~ $ git grep "文件内容" ~~~ #### 显示版本库.git目录所在位置 `$ git rev-parse --git-dir` #### 显示工作区根目录 ~~~ $ git rev-parse --show-toplevel ~~~ #### 显示当前目录相对于工作区根目录的相对目录 ~~~ $ git rev-parse --show-prefix ~~~ #### 显示从当前目录后退到工作区的根的深度 `$ git rev-parse --show-cdup` #### 编辑配置文件 ~~~ $ git config -e 编辑版本库级别的配置文件(需要进入工作目录的根目录) $ git config -e --global 编辑用户主目录下的全局配置文件 $ git config -e --system 编辑/etc目录下的系统级配置文件 ~~~ 上面三个配置文件优先级从高到低 配置文件采用了ini文件格式 ~~~ $ git config
. 读取配置文件某个配置的键值 $ git config
. 修改某个配置的键值 ~~~ 还可以用git config命令操作任何其他的ini文件 ~~~ $ GIT_CONFIG=test.ini git config a.b.c.d "hello" 向test.ini中添加配置 $ GIT_CONFIG=test.ini git config a.b.c.d 从test.ini中读取配置 ~~~ #### 查看日志 ~~~ $ git log ~~~ log的参数: --pretty=fuller 额外显示提交日期 --stat 显示每次提交的文件变更统计 Git克隆可以降低因为版本库和工作区混杂在一起而导致的版本库被破坏的风险。可以通过克隆操作在本机另外的磁盘/目录中建立Git克隆,并在工作区有新的提交时,手动或自动执行向克隆版本库的推送(git push)操作。如果使用网络协议,还可以在其他机器上建立克隆。
';

(一) Git的安装与使用

最后更新于:2022-04-01 19:53:29

软件包git-doc包含了Git的HTML格式的文档。可以通过执行git help -w 命令自动用web浏览器打开相关子命令的HTML帮助。 ### 从Git版本库进行安装的方法: 如果在本地克隆一个Git项目的版本库,就可以用版本库同步的方式获取最新版本的Git,这样在下载不同版本的Git源代码时实际上采用了增量方式,非常节省时间和空间,前提是已经用其他方法安装好了Git。具体过程如下: (1)克隆Git项目的版本库到本地: ~~~ $git clone git://git.kernel.org/pub/scm/git/git.git $cd git ~~~ (2)如果本地已经克隆过一个Git项目的版本库,直接在工作区中更新,以获得最新版本的Git。 ~~~ $git fetch ~~~ (3)执行清理工作,避免前一次编译的遗留文件对编译造成影响。注意,下面的操作将丢弃本地对Git代码的改动。 ~~~ $git clean -fdx $git reset --hard ~~~ (4)查看Git的里程碑,选择最新的版本进行安装。 ~~~ $git tag ~~~ (5)检出该版本的代码 ~~~ $git checkout v1.7.4.1 ~~~ (6)执行安装。例如安装到/usr/local目录下(注:若报错openssl/ssl.h: 没有那个文件或目录,则需$ sudo apt-get install libssl-dev;若报错curl/curl.h,则需apt-get install libcurl4-gnutls-dev) 但还是有错,网上也没找到解决方法…… ~~~ $make prefix=/usr/local all doc info $sudo make prefix=/usr/local install \ install-doc install-html install-info ~~~ ### Git对中文的支持: 可以在提交说明中使用中文,但是需要对Git进行设置。至于用中文命名文件、目录和引用,只有在使用UTF-8字符集的环境下才可以,否则应尽量避免使用。 1.UTF-8字符集 提交时,可以在提交说明中输入中文;显示提交历史,能够正常显示提交说明中的中文字符;可以添加名称为中文的文件,并可以在同样使用UTF-8字符集的Linux环境中克隆和检出;可以创建带有中文字符的里程碑名称。但是在默认设置下,中文文件名在工作区状态输出、查看历史更改概要,以及在补丁文件中,文件名中的中文不能正确显示,而是显示为八进制的字符编码。设置$git config --global core.quotepath false可以解决中文文件名在这些Git命令输出中的显示问题。 2.GBK字符集 若Linux平台采用非UTF-8的字符集,就要另外再做些工作。 将显示提交说明所使用的字符集设置为gbk,这样使用git log查看提交说明时才能够正确显示其中的中文$git config --global i18n.logOutputEncoding gbk 设置录入提交说明时所使用的字符集,以便在commit对象中正确标注字符集$git config --global i18n.commitEncoding gbk。Git在提交时不会对提交说明进行从GBK字符集到UTF-8的转换,但是可以在提交说明中标注所使用的字符集,因此在非UTF-8字符集的平台中录入中文时需要用此指令设置录入提交说明的字符集,以便在commit对象中嵌入正确的编码说明。
';

前言

最后更新于:2022-04-01 19:53:26

> 原文出处:[git学习笔记](http://blog.csdn.net/column/details/gitnote.html) 作者:[agul_](http://blog.csdn.net/agul_) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # git学习笔记 > 学习Git过程中的学习笔记(使用的书为《Git权威指南》http://book.douban.com/subject/6526452/)
';