git学习练习总资源连接: https://try.github.io/ (练习已通,有document)javascript
本沙盒游戏教学:https://learngitbranching.js.org/?demo java
自由沙盒模拟网页 : http://git-school.github.io/visualizing-git/git
好的译文: https://github.com/geeeeeeeeek/git-recipes/wikigithub
什么是git?
一个分布式的源代码库。管理Linux内核源代码。正则表达式
git已快照形式保存和处理内容,每个提交都是一次快照。git能够在快照之间回滚。vim
一个节点表明一个commit.api
*表明当前分支的最后一次提交:HEAD缓存
master是主干。安全
其余名字是分支。 并发
git merge :用于合并分支的代码。
git rebase : 线性合并分支:
git rebase [-i] [目标] [移动记录]
git rebaes [目标] #省略[要移动的记录],则为当前分支的全部commit。
假如当前分支是bugFix:
- git rebase master. 这样bugFix分支就至关于在master的基础上新增的代码了。
- git checkout master 回到master
- git rebase bugFix, master和bugFix的代码都同样了。
HEAD:
是一个对当前检出记录的符号引用 -- 也就是指向你正在其基础上进行工做的提交记录
它老是指向当前分支上最后一次的提交记录。 大多数提交树的git命令都是从改变HEAD的指向开始的。
⚠️,后面章节讲的远程分支 origin/master是例外
HEAD 一般是指向分支名的(如bugFix)。在你提交commit时,改变了分支的状态,这一变化经过HEAD变得可见。
分离的HEAD:
让它指向某个具体的提交记录(hash值)而不是分支名。
git checkout <hash>
git checkout命令本质就是移动HEAD,到目标commit点, 而后更新工做目录以匹配这个commit点。
由于这个操做会overwrite local changes,致使改变的文件丢失,因此Git强迫你先commit或stash工做目录中的改变的文件。
⮀ git checkout master error: Your local changes to the following files would be overwritten by checkout: app/assets/javascripts/search.js Please commit your changes or stash them before you switch branches.
关于git stash (具体工做原理和所有的知识见连接文章)
会把还没有加入stage的文件和statge中的文件保存(就是未commited的文件),以便在以后使用。
以后能够revert them from your working copy.
如今能够进入任何其余操做,如建立新commits, 转变分支,执行其余git操做了。
⚠️stash是本地的。当你push的时候,stash不会被传输。
Re-applying your stashed changes:
$ git stash pop
另外使用git stash apply, 能够reapply the changes的同时在stash中保留它们。这在为多个分支应用时有用。
⚠️:默认Git不会stash 未tracked文件和ignored files。
相对引用
经过指定提交记录hash值的方式在Git中移动不方便操做。
必须用到git log, 并且hash值很是长。
所以能够只使用前几个字符表明一个提交记录 , 即“相对引用”。
^ 代表👆向上移动一个commit记录。
~2 表明向上移动2个提交记录,~5,表明移动5个提交记录
使用git checkout HEAD^, 就表明向上移动一次。
强制修改分支位置--移动分支
git branch -f master HEAD~3
表明把master向上移动三个提交节点,即第3个father note
-f :表明--force, force creation, move/rename, deletion
撤销变动
- git reset
- git revert
Command | Scope | Common use cases |
---|---|---|
git reset |
Commit-level | Discard commits in a private branch or throw away uncommited changes |
git reset |
File-level | Unstage a file |
git checkout |
Commit-level | Switch between branches or inspect old snapshots |
git checkout |
File-level | Discard changes in the working directory |
git revert |
Commit-level | Undo commits in a public branch |
git revert |
File-level | (N/A) |
Git Reset
原文:
takes a specified commit and resets the "three trees" to match
the state of the repository at that specified commit.
Three Trees
把分支回退指定个数的commit记录,来实现撤销改动。至关于使用时间机器回退到过去开发的阶段。(撤销的提交记录还存在,只是未加入stage暂存区)
⚠️ 这条命令对团队使用的远程分支无效!
例子:
# 从当前工做目录回退一个commit. git reset HEAD~ # 从当前工做目录回退2个commit git reset HEAD~2
⚠️被回退的2个commit提交变成悬挂提交。下次Git执行垃圾回收时,这两个提交会被删除!
git reset能够把stage中的文件拿出stage。git reset </filename>
⭠ autoquery± ⮀ git reset README.md Unstaged changes after reset: M README.md
三个模式选项
- --soft -stage缓存区和工做目录都不会改变。
- --mixed默认选项。✅
- 缓冲区和你指定的提交同步a(在提交a后,加入缓存区的文件被拿出来了)
- 工做目录不受影响(在提交a后对工做目录中的文件进行的改变被保留!)。
- --hard -缓存区和工做目录都被更新,以匹配指定的commit点a。
- 至关于回退到刚刚提交完a的状态!
- 在提交a后的操做所有删除,包括工做目录中对文件的改变,至关于时光倒流
能够认为这个三个模式是对a git reset操做的做用域的定义!
通常使用默认的--mixed。
Git Revert (点击见详细)
专门用于撤销远程提交记录。但本质上是新增一个commit记录,但更改了code,去掉了以前那个commit记录中的变动代码。
c0-c1-c2(->c3)
git revert C1, 结果是新增了一个c3.
c3是c1的反转操做,即c1中变化的代码,在c3中被撤销了。
例如, 你追踪一个bug并发现它是在某个commit点a内增长的一个变量。你无需手动进入这个commit点,删除这个变量,而后再committing一个新的snapshot,你直接使用git revert a命令自动为你作上面的事情。
⚠️:若是你revert提交点c1, 可是c2中有对c1中变化的代码的进一步修改,你不能使用git revert c1, 系统会提示你冲突,须要先搞定冲突代码。
How it works
git revert命令用于撤销一个仓库的commit历史中的某个变化。
其余撤销命令如git checkout 和 git reset,移动HEAD和branch ref pointers到一个指定的commit点。
Git revert也take a specified commit,可是,git revert不会移动ref pointers到这个commit点。
而是执行一个反转操做,反转那个commit的改变的代码,并建立一个新的“revert commit”。
最后ref pointers 会更新,指向这个新的"revert commit", 让这个commit成为分支的端点tip。
选项
-e --edit (默认选项) 打开系统的编辑器,提示你编辑commit信息。
-n --no-commit (一个特别的选项)使用它,git revert不会建立新的commit, 只会在working directory反转变化的代码,并在缓存中加入反转代码后的文件。一句话理解:须要你手动提交!其余没变化。
和get reset的比较:
1:不会改变历史记录。
- 基于这个优势git revert能够在一个public branch上使用,
- 而git reset最好在我的的branch上进行操做。
2:git revert只改变单一的提交点。而git reset会从当前提交往回退(回到过去)。
能够这么理解:
- git revert是撤销committed changes的工具
⚠️git revert也和git checkout相似,在revert操做期间会重写工做目录中的文件。因此它会要求你commit/stash changes。
File-level Operations
git reset和git checkout命令能够接收一个file path做为参数。这会强制把它们的操做限制到一个单一文件。
例子:
git reset HEAD~2 foo.py
当引用一个文件路径时, git reset更新缓存区以匹配特定commit点的版本的指定文件。
👆的命令会取得在提早两个提交点的版本的foo.py文件,并把它放入Staged files缓存区中。
可是工做区出现对应的文件的unstaged 状态:
⭠ autoquery ⮀ git reset head~3 README.md Unstaged changes after reset: M README.md ⭠ autoquery± ⮀ git status On branch autoquery Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.md Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: README.md
个人理解:
- 执行git reset head~2 foo.py命令
- 取得的文件foo.py放入缓存区,但程序发现取出的文件和当前的foo.py文件的内容不一致,因此出现👆的状况。
另外,若是我修改了上一个commit点的foo.py文件并放入缓存区。
而后又执行git reset head~2 foo.py。
程序发现取出的文件和当前修改的foo.py文件的内容不一致,会把当前修改的foo.py文件放入工做目录区。
⚠️复杂的操做尽可能配合可视辅助工具Sourcetree
Git checkout head File命令
它会把file放入working directory工做目录。
⚠️个人实际操做是使用此条命令后,取出的文件会放入到缓存区!个人理解:
- git checkout head File
- 程序自动把取出的文件执行git add命令,放入缓存区。
git checkout HEAD~2 foo.py
⚠️此时git checkout命令不会移动HEAD标签,即你不会切换分支!
⚠️:若是把这个变化git commit就至关于直接执行一个git revert命令了!!
总体提交记录
当开发人员说,我想把这个commit放到这里,那个commit放到刚才的提交的后面,就可使用:
将一些commit复制到当前位置HEAD下面的话,使用这个命令:
git cherry-pick <提交号>...
⚠️我的理解,是每一个commit应该是一个独立的模块。
⚠️,前提是你知道你想要的commit记录的hash值,才行。若是不知道,往下看⬇️
交互式的rebase
git rebase -i HEAD~3
指带参数 --interactive的rebase命令,简写-i
rebase会打开一个UI界面:
git 技巧 1: 本地stage提交
假如当前在bugFix分支(C4), 而master在C1, 但愿只把C4合并到master上。
c1(master)-c2-c3-c4(bigFix*)
第一种办法:
git checkout master#HEAD回到master
git cherry-pick C4#ok了
第二种办法:
git rebase -i C1 #会打开UI界面,选择C4,去掉C2, C3 ,肯定。
#这时buFix是当前分支它包含C4直接放到了C1下面。
git checkout master #回到master, 由于master的数据比较旧
git merge bugFix#合并分支。
git 技巧 2:
git commit --amend
修复最新提交的便捷方式。做用是:
将缓存的修改和以前的commit合并到一块儿,生成一个新的提交并替换掉原来的提交。
这是从写项目历史的命令。
讨论:
仓促的提交在你平常开发过程当中时常会发生。很容易就忘记了缓存一个文件或者弄错了提交信息的格式。--amend
标记是修复这些小意外的便捷方式。
注意:⚠️
不要修复public commit! 永远不要重设和其余开发者共享的commit。
修复也同样:永远不要修复一个已经推送到公共仓库的commit!
https://learngitbranching.js.org/?demo (点击连接看演示, 而后选择第四行第2个按钮)
c1(master)—c2(newImage)—c3(caption*)
设计师须要在c2上调整图片格式,如何作?
|再问:
设计师干吗不在C3上调整?
答:猜想C2提交记录是针对图片的设计,c3提交记录是针对其余设计。
假设:
git checkout newImage
git commit --amend
git checkout capiton
git merge newImage #这会致使新产生一条commit记录。C4, 不符合线性的要求。
而使用:
# 调整C2, C3的位置,让C2位于分支tip, 由于git commit --amend用于最新提交点。 git rebase -i C1 # 对C2进行修改 git commit --amend #再调整回原先的结构。 git rebase -i C1 #至关于,历史commit记录树没有发生变化。
#而后就能够合并了
git checkout master
git checkout caption
⚠️,做者提示这可能会致使冲突:改用挑🍒,更快捷。cherry-pick
git checkout newImage
git commit --amend #这会出现一个分叉。
git checkout master
git cherry-pick C2' C3
标签的做用:tag
给某个提交记录一个标签,相似⚓️。 用于重要版本的标记。
git tag V1 <hash>
若是不指定<hash>提交记录,则标记到HEAD指向的位置,所以能够写两条语法:
git checkout <hash>
git tag V1
Git Describe
用来找到最近的tag。帮助你在commit record的历史中移动了屡次后找到方向。
git describe <ref>
<ref>是任何识别commit记录的引用,不指定的话,则以当前HEAD位置为准。
它的输出结构:
<tag>_<numCommits>_g<hash>
解释:
<tag>是离<ref>最近的标签,
numCommits是表示和<ref>相差多少个commit记录,
hash表示的是你所给定的<ref>所表示的提交记录hash值的前几位。
Pushing tags
默认,标签不会自动推送上去。 --tags将你全部的本地标签推送到远程仓库。
git push <remoteName> <TagName>
push all tags:
git push <remoteName> --tags
挑战1:线性移动合并分支:git rebase (能够看sandbox案例)
git rebase [-i] [目标] [要移动的记录]
git rebaes [目标] #省略[要移动的记录],则为当前HEAD
挑战2: 使用HEAD~和HEAD^2来移动HEAD的位置
相对引用的扩展:
^2表明第二个父引用记录,能够链式使用。
git checkout HEAD~^2~
表示上一个父记录,而后再第二个父记录,而后再上一个父记录。
若是要在这里创建一个新分支
git branch bugFix HEAD~^2~
挑战3 ,git rebase 的再次使用。
HEAD也能够作[目标]
git rebase [-i] [目标] [要移动的记录]
remote repertory
简单来讲就是你的仓库在其余机器上的备份。
特色:
- 备份,恢复丢失数据
- 远程让代码能够社交化了!其余人能够为你的代码作贡献。
git clone: 在本地建立一个远程仓库的拷贝
远程跟踪分支 remote-tracking branch
在本地仓库多了一个名为o/master的分支,这种类型的分支叫作 远程跟踪分支。
远程跟踪分支反应了远程仓库(在你上次和它通讯时)的状态。
这有助于理解你本地的工做和公共工做的差异 -- 这是和别人分享工做成果最重要的一步。
特别的属性:在你checkout时,自动进入分离的HEAD状态。 缘由是Git要求,不能在远程跟踪分支上直接写代码,须要先在其余地方写好代码后,更新到远程仓库对应的位置,远程跟踪分支才会更新。
git checkout o/master;
git commit;
在有新的commit提交记录后,o/master不会同步更新,会和HEAD分离。
⚠️,Head老是指向当前分支上最后一次的提交记录,但这里是例外。
o/master只有在远程仓库中对应的分支更新后,才会更新。
格式:<remote name>/<branch name>
简称: o/
默认: remote name 是 origin
Git Fetch
从remote repertory得到数据。
当从远程仓库得到数据时, 远程分支会自动更新,以反应最新的远程仓库。
git fetch会作的事情:
- 从远程仓库下载本地仓库缺乏的commit记录
- 更新远程跟踪分支 如 origin/feature_branch
- ⚠️不会更新你的master分支和其余分支,也不会修改你磁盘上的文件。
- 所以,不是说git fetch后本地仓库就和远程仓库同步了,这是❌的想法。
- git fetch只是单纯的下载服务。
//取指定的分支或tags,下载全部须要的commits和文件 git fetch <repository> [<refspec>...] // 取全部remote branch git fetch --all [<options>]
// 试运行,就是一次彩排,看看会出现什么结果
git fetch --dry-run
使用git fetch 同步远程仓库:
git fetch origin //会显示咱们下载的branchs
//a1e8fb5..45e66a4 master -> origin/master
//a1e8fb5..9e8ab1c develop -> origin/develop
//* [new branch] some-feature -> origin/some-feature
若是想要查看上游master增长了什么commit,能够运行git log命令,并使用origin/master进行检索:
git log --oneline master..origin/master
而后批准这些变化并合并它们到你的本地master分支:
git checkout master git log origin/master
//如今origin/master和master分支指向同一个commit点了,
//而且你和上游upstream 开发同步了。 git merge origin/master
Git Pull
下载下来后,把远程分支合并到本地的方法:
- git cherry-pick o/master
- git rebase o/master
- git merge o/master
- 等等
git pull 就是直接一步完成2个命令。即git fetch和git merge的方便代码。
等同于:git fetch 和git cherry-pick o/master
效果等同于:git fetch, git rebase master o/master, git rebase o/master master。但结构是线性的,由于使用git rebase是线性合并,o/master会指向新C3'
模拟团队合做:下载。
- git fetch
- 在本地的master分支新增了commit
- git merge o/master, 合并远程分支到本地的master。
Git Push
当本地仓库被修改,须要执行git push操做来分享这个修改的代码给team members:
git push <remote> <branch>
git push <remote> -all
若是不带任何参数,会使用push.default的设置。他的默认值是正在使用的Git的版本,推送前最好检查一下这个配置
偏离的困惑
假如周一你克隆了一个仓库,而后开发某个新功能。到周五时,能够提交到远程仓库了。可是,
这周你的同事写了一堆代码并修改了你还在使用的API。这些变更让你的新开发的功能不可用。
但他已经提交推送到远程仓库了。你的工做变成了基于旧版本的代码,已和远程仓库的最新的代码不匹配了。
这时,Git不会容许你push,它会要求你先合并远程最新的代码,而后你才能分享你的工做。
这就是历史偏移
须要:
- git fetch
- git rebae o/master#这里可能你的新增功能,会失效,
- 你须要先修改代码,而后commit。最后:
- git push
- git fetch#更新本地仓库中的远程分支
- git merge o/master#这会产生新的commit记录。C4
- git push
--rebase选项:
git pull --rebase , 等同于用git rebase合并远程分支,而默认是git merge
小结:工做流程: fetch, rebase/merge, push
强制push
Git 为了防止你覆盖中央仓库的历史,会拒绝你会致使非快速向前合并的推送请求。
--force
这个标记覆盖了这个行为,让远程仓库的分支符合你的本地分支,删除你上次 pull 以后可能的上游更改。
只有当你意识到你刚刚共享的提交不正确,并用 git commit --amend
或者交互式 rebase 修复以后,你才须要用到强制推送。
⚠️ 可是,你必须绝对肯定在你使用 --force
标记前你的同事们都没有 pull 这些提交。
关于origin和它的周边 --Git 远程仓库高级操做
推送push 主分支
大型项目,开发人员会在特性分支上工做,工做完成后只作一次集成。
但有些开发者只在master上push,pull。这样master老是最新的,始终与远程分支保持一致。
- 将特性分支集成到master上。
- push并更新远程分支
为何操做远程分支不喜欢用merge, 见仁见智
- 喜欢干净的提交树,用rebase
- 喜欢保留提交历史的,用merge
远程跟踪分支:remote-tracking branches
Git 设置了master和o/master的关联。
- pull时,commit记录会被先下载到远程分支,如:origin/master上,以后会再合并到master分支。
- push时,咱们把工做从master推到远程仓库中的master分支,同时更新远程分支origin/master。
它们的关联关系,是由"remote tracking" 属性决定的。
master被设定为跟踪o/master。
当你克隆远程仓库时,Git会为远程仓库的每一个分支都设定一个远程分支,而后再在本地建立一个和远程仓库
中的分支同样的本地分支。
克隆完成后,你会获得本地分支,若是没有就是空白。
这也解释了在克隆时会看到下面的输出:
local branch "master" set to track remote branch "o/master"
本地分支“master”设置跟踪远程分支 "o/master"
能够指定"remote tracking" 属性属性
让任意分支跟踪o/master,而后该分支就会像master分支同样获得隐藏的push目的地和merge的目标。
这意味着你能够在分支XXX上指向git push, 将工做推送到远程仓库的master分支上。
两种设置方法:
- git checkout -b XXX o/master
- 若是已经有了XXX分支,则使用git branch -u o/master XXX
Git push的参数
默认Git经过当前checkout分支的属性来肯定远程仓库和要push的目的地。
咱们也能够明确指定push的参数:
git push <options> <remote-Repository-name> <place>
例子:
git push --set-upstream origin master:
把当前分支push, 并设置远程仓库origin为upstream。
解释:
这行代码通常用在建立一个远程连接并同步上传数据:
⭠ master ⮀ git remote add origin https://github.com/xxxxxx/yyyyyy.git
//连接远程仓库origin.
⭠ master ⮀ git push -u origin master //Branch master set up to track remote branch master from origin. //本地分支master已经开始追踪远程仓库origin的master分支!
<place> 详细解释:
能够分解为<localbranch-source>:<destination>
即本地分支source,提交到远程的另外一个分支destination。
<source>能够是任何commit记录位置。
若是<destination>在远程仓库中并不存在,则会在远程仓库中新建这个分支。
例子: git push origin localbranch:remotebranch
强制执行一次non-fast-forward merge
git push origin --force
⚠️,只有绝对肯定你正在作的才这么用!
--all选项:推送全部分支:
git push <remote> --all
把标签也推送上去,默认tags是不推送的。
git push <remote> --tags
Amended force push
git commit --amend 用于更新提交点,
本质就是把commit原先的改变和更新的内容存入一个新的commit点,扔掉原来的commit点。
可是如此,git push 会致使失败,由于Git会发现Amended的commit点和远程的commit点的内容是分离的。
此时须要使用--force选项来push一个amended commit!
# make changes to a repo and git add git commit --amend
#此时若是直接git push 会报告错误❌:
# ! [rejected] master -> master (non-fast-forward)
#须要使用--force
git push --force origin master
注意⚠️没有同步更新remote-tracking branch!
当本地master分支和origin/master在一块儿时,执行一次修改并commit,再git push, 这时origin/master不能同步更新,须要再次执行一次同步命名。git push ,git fetch均可以.
再次提交一次:
#修改一些文件并提交 git commit -am 'd'
能够发现origin/master是处于4ebc769,
若是使用
git fetch
origin/master会更新到最新commit点。
删除远程分支或者tag
实际是推送一个空的分支替换远程一个分支,至关于删除远程的这个分支。
//查看本地和远程的分支 git branch --all //删除本地分支 git branch -D branch_name //删除远程分支 git push origin origin :branch_name
Git remote
如⬆️代码,git remote add origin <url>, 建立了本地和远程仓库origin的连接!
本地./.git/config文件储存了这条记录信息:
[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = https://github.com/chentianwei411/practice.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master config (END)
git push -u origin master命令,则添加了[branch "master"]这个记录。
全部git remote的操做,都会记录在./.git/config文件中!
//执行git remote remove origin命令: //会断开本地和远程的链接:
//全部对跟踪远程的分支的设置和对远程的配置设置被移除!
//结果: [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [branch "master"] config (END)
再次使用git remote add origin <url>, 便可再链接上远程仓库:见./.git/config文件增长的代码:
[branch "master"] [remote "origin"] url = https://github.com/chentianwei411/practice fetch = +refs/heads/*:refs/remotes/origin/*
git remote get-url --all origin
列出全部的远程链接URLs.
git remote show <name>
这条命令会给出详细的关于一个远程链接的配置信息.
先git remote -v命令,查看。而后用git remtoe show <name>:
- show origin,
- show upstream ,
- shou other_users_repo
* remote origin Fetch URL: https://github.com/chentianwei411/practice Push URL: https://github.com/chentianwei411/practice HEAD branch: master Remote branch: master tracked Local ref configured for 'git push': master pushes to master (up to date)
git remote prune [--dry-run] origin
Deletes all stale remote-tracking branches under <name>.
删除全部过时的远程跟踪分支, 即origin/xxx。
⚠️origin/xxx是存在于本地仓库的,用于接收从远程仓库pull下来的数据。而后再merge到xxx分支。
These stale branches have already been removed from the remote repository
referenced by <name>, but are still locally available in "remotes/<name>".
这些过时的分支已经从远程仓库origin移除了,可是在本地仓库仍然能够在remotes/origin中找到:
⭠ master ⮀ git branch --all hotfix * master try remotes/origin/hotfix remotes/origin/master
--dry-run, 列出什么分支会被剪除掉prune!
⮀ git remote prune --dry-run origin Pruning origin URL: https://github.com/chentianwei411/practice * [would prune] origin/hotfix
⮀ git remote prune origin Pruning origin URL: https://github.com/chentianwei411/practice * [pruned] origin/hotfix
再次使用git branch -a命令查询全部分支,会发现remotes/origin/hotfix分支已经被删除!!
显示你的remotes
git remote
git remote -v
//-v选项,是verbose的意思 // 会列出标记的仓库名字和相关信息,合做的仓库URL. origin git@bitbucket.com:origin_user/reponame.git (fetch) origin git@bitbucket.com:origin_user/reponame.git (push) upstream https://bitbucket.com/upstream_user/reponame.git (fetch) upstream https://bitbucket.com/upstream_user/reponame.git (push) other_users_repo https://bitbucket.com/other_users_repo/reponame (fetch) other_users_repo https://bitbucket.com/other_users_repo/reponame (push)
添加远程仓库
当你添加了一个远程仓库。 你就可使用仓库名字origin做为<url>的简写,在其余git命令上使用了。
这是由于./.git/config中记录了url的信息:
[remote "origin"] url = https://github.com/chentianwei411/practice fetch = +refs/heads/*:refs/remotes/origin/*
Git Fetch origin <remotebranch-source>
和git push正相反,Git会查找remotebranch的本地远程分支,并下载到本地的远程分支。
这样不会弄坏你的本地同名分支。
也能够另外指定远程仓库的分支下载到哪一个本地的分支<destination>,
这样会直接下载到某个分支上(不是远程分支),⚠️开发人员不多这么作,有风险。
若是本地没有一个bar分支:
git fetch origin XX:bar的结果是, 会在本地自动建立一个bar分支,用来存储远程仓库的commit信息。
若是只有git fetch:
Git会下载远程仓库中全部的提交记录到各个远程分支...
省去<source> 的特殊用法:
删除远程仓库的分支:
git push origin :foo #推送一个空的source到远程仓库,若是远程仓库有foo分支,这个分支将被删除。
git fetch origin :bar #下载一个远程仓库没有的空分支给本地,本地建立一个bar分支。
Git Pull参数
git pull origin foo 至关于:
git fetch origin foo; git merge o/foo
⚠️git pull origin master 会先下载到o/master, 而后merge到当前checkout检出位置。
因此,使用git pull要不熟练,要不就别用。
git pull也可使用<source>:<destination>:
如:git pull origin master:foo
- 若是本地没有foo, 先在本地建立一个foo分支,
- 而后从远程仓库下载master分支中的提交记录并合并到foo分支
- 而后再merge到当前的检出分支checkout分支上。
git clean
将未跟踪的文件从你的工做目录working directory中移除。
未跟踪文件是新增到在工做目录但还没有添加到用 git add添加到repo's index。
它只是提供了一条捷径,由于用 git status
查看哪些文件还未跟踪而后手动移除它们也很方便。
和通常的 rm
命令同样,git clean
是没法撤消的,因此在删除未跟踪的文件以前想清楚,你是否真的要这么作。
git clean
命令常常和 git reset --hard
一块儿使用。
⚠️记住,reset 只影响被跟踪的文件,因此还须要git clean来清理未被跟踪的文件。这个两个命令相结合,你就能够将工做目录回到以前特定提交时的状态。
# 先测试一下比较好-n ,--dry-run
git clean --dry-run
# 移除当前目录下未被跟踪的文件。-f(强制)标记是必需的。
# 不会删除未跟踪的目录directory和.gitignore中的文件。 git clean -f # 移除未跟踪的文件,但限制在某个路径下 git clean -f <path> # 测试移除未跟踪的目录directory git clean -dn
# 移除未跟踪的目录
git clean -d
⚠️请牢记,和 git reset
--hard 同样, git clean
是仅有的几个能够永久删除提交的命令之一,因此要当心使用
栗子:
# 编辑了一些文件 # 新增了一些文件 # 『糟糕』 # 将跟踪的文件回滚回去, 新增文件从head, index, 工做目录中删除了!!修改的文件恢复到以前的代码!! git reset --hard # 移除未跟踪的文件,先测试-n, -dn
git clean -n git clean -df
改变旧的或者多个commits: git rebase
rebase改基。从一个分支移动到另外一个分支,即改基。
移动或联合一系列的提交点到一个新的base commit。实际是建立了一系列新的提交点。
效果和目的:
⚠️不要用在public commit。
- 让你修改你的历史, 而且可交互的改基容许你如此作而不留下杂乱的痕迹。
- 让你在修改错误和重新定义你的任务后,仍然保持了一个干净,线性linear的程序历史。
在真实的场景:
- 在主分支发现bug.。一个功能分支的功能由此坏掉。
- 开发者检查主分支历史git log。 由于clean history,开发者能快速的找出project的历史。
- 开发者使用git log仍是不能识别出bug是什么时候插入的introduced。因此他执行了git bisect
- 由于git history很干净, git bisect有一个refined set of commits 来比较。开发者快速的找到了这个插入bug的commit点。
例子:
当你在一个功能分支上进行开发时,主分支发现一个bug。
新增一个hotfix分支,用于fix bug。 在搞定bug后, 把bug分支合并到master。
你想要在你的功能分支上使用最新版本的主分支,但你想要保持你的功能分支的历史干净,即好似你一直在最新版的master上开发功能分支。
# 开始新的功能分支 git checkout -b new-feature master # 编辑文件 git commit -a -m "Start developing a feature" #在 feature 分支开发了一半的时候,咱们意识到项目中有一个安全漏洞:---- # 基于master分支建立一个快速修复分支 git checkout -b hotfix master # 编辑文件 git commit -a -m "Fix security hole" # 合并回master git checkout master git merge hotfix git branch -d hotfix #将 hotfix 分支并回以后 master,咱们有了一个分叉的项目历史。---- git checkout new-feature git rebase master #它将 new-feature 分支移到了 master 分支的末端, #而后在master上进行标准的快速向前合并了: git checkout master git merge new-feature
Rebasing的一个经常使用方式是:把upstream的变化集成到你的本地仓库。
移动整个功能分支⬆️
git rebase -i <base>
用 -i
标记运行 git rebase
开始交互式 rebase。交互式 rebase 给你在过程当中修改单个提交的机会,而不是盲目地将全部提交都移到新的基上。你能够移除、分割提交,更改提交的顺序。
讨论
交互式 rebase 给你了控制项目历史的彻底掌控。它给了开发人员很大的自由,由于他们能够提交一个「混乱」的历史而只需专一于写代码,而后回去恢复干净。
大多数开发者喜欢在并入主代码库以前用交互式 rebase 来完善他们的 feature 分支。他们能够将不重要的提交合在一块儿,删除不须要的,确保全部东西在提交到「正式」的项目历史前都是整齐的。对其余人来讲,这个功能的开发看上去是由一系列精心安排的提交组成的。
执行:git rebase -i master 后出现vim的交互界面:
- 若是删除全部pick,而后:wq保存退出,至关于取消rebase 这条命令。提示:Nothing to do
- 若是直接:q退出,不作任何修改,至关于执行了git rebase master命令。
- 提示:Successfully rebased and updated


若是只pick 37c8c61,第一行。则提示成功:
Successfully rebased and updated refs/heads/b2.

git checkout master git merger b2
⚠️另外2个pick就被分支b2扔掉了!!
若是知道这2个commit的id,就能够进入它们:
git chekcout xxxx
获得提示信息:You are in 'detached HEAD' state. 你处于分离的HEAD!
能够把这2个提交点合并到b2, 再把b2合并到master。
Rebasing的其余几个有趣的命令:
- edit
- reword -能够重写提交信息message
- squash -把当前的commit合并到上一个commit, 并提示你重写提交message
- fixup -和squash同样,不会重写提交message.
edit 9a29b81
如可使用命令edit, 代替pick,当:wq保存退出vim后,提示
⭠ b3 ⮀ git rebase -i master Stopped at bb28411... change yangcheng You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
➦ bb28411 ⮀
而后你能够在这个提交点上作修改,而后
➦ bb28411± ⮀ git add . ➦ bb28411± ⮀ git commit --amend [detached HEAD 36f12d6] change yangcheng Date: Sun Nov 25 11:45:44 2018 +0800 1 file changed, 1 insertion(+), 1 deletion(-) ➦ 36f12d6 ⮀ git rebase --continue Successfully rebased and updated refs/heads/b3. ⭠ b3 ⮀
squash 9a29b81
合并后放入新增的一个commit中。 这是rebase的主要功能!!
缘由:
大量的细小的改动,每一个改动都是一个提交。致使仓库的history看起来很乱。
许多这样的commit并无实际地给你的仓库history增长任何价值
它们弄乱了blame(新旧版本的对比), make bisects take longer and make the history hard to navigate.
⭠ b4 ⮀ git rebase -i master
#此时进入vim编辑器。重写提交message,而后:wq
[detached HEAD fac0e93] change content and guangzhou Date: Sun Nov 25 12:42:45 2018 +0800 2 files changed, 3 insertions(+), 1 deletion(-) Successfully rebased and updated refs/heads/b4.
而后:
git checkout master git merge b4
git branch -d b4
还有另外一种使用squash的方式:
git merge --squash <commit> 能够把一个分支合并为一个commit, 而后执行
git commit -m 'squash ...'
最后删除分支b3: git branch -D b3
安全网:git reflog
Git保持对分支的tip的追踪!这种机制叫作reflog(reference logs), 引用日志。
Git 用引用日志这种机制来记录分支顶端的更新和其余commit引用。
它容许你回到那些不被任何分支或标签引用的commits。在重写历史后,reflog包含了分支旧状态的信息,有须要的话你能够回到这个状态。
每次当你的分支tip被任何缘由所更新(包括切换分支,pulling in new changes, 重写历史或者仅仅是增长新的commits), 一个新的entry将被增长到reflog。
reflog提供了一张安全网,全部的分支顶端的变化都会被记录。
另外, reflog提供了到期日。默认设置expiration time是90天。
用法
git reflog # 是git reflog show HEAD的简写
显示:
fac0e93 HEAD@{0}: merge b4: Fast-forward 6f70a12 HEAD@{1}: checkout: moving from b4 to master fac0e93 HEAD@{2}: rebase -i (finish): returning to refs/heads/b4 fac0e93 HEAD@{3}: rebase -i (squash): change content and guangzhou 0481ff2 HEAD@{4}: rebase -i (pick): change content 6f70a12 HEAD@{5}: rebase -i (start): checkout master 3bc3338 HEAD@{6}: checkout: moving from master to b4 。。。
#fac0e96,第一行是最新的一次relog。
还能够用:
git reflog --relative-date
fac0e93 HEAD@{3 hours ago}: merge b4: Fast-forward
6f70a12 HEAD@{3 hours ago}: checkout: moving from b4 to master
fac0e93 HEAD@{4 hours ago}: rebase -i (finish): returning to refs/heads/b4
...
#显示相对如今,每条记录发生的时间!
默认,git reflog会输出HEAD ref。但也能够显示其余ref。
如其余分支,tags, remotes, Git stash均可以被引用。
引用中的语法格式:name@{qualifier}
得到所有的reflog:
git reflog show --all
查看具体某一个分支的引用:
⭠ b5 ⮀ git reflog show b5 //显示: 905b28c b5@{0}: commit: change bj fac0e93 b5@{1}: branch: Created from HEAD (END)
若是使用过git stash命令储存了缓存区的文件, 而且还未取出,则能够用git reflog命令查看记录:
git reflog stash
//显示
bd1f5f7 stash@{0}: WIP on b5: 905b28c change bj
另外, 可使用git diff: Show changes between commits, commit and working tree
//git diff stash@{0} otherbranch@{0}
⭠ b5 ⮀ git diff stash@{0} b5@{0}
//显示 diff --git a/beijing.txt b/beijing.txt index 7719339..6815264 100644 --- a/beijing.txt +++ b/beijing.txt @@ -1,3 +1,4 @@ +123 hahaha hello hello this is a beautiful city! (END)
通常用不到,使用图形编辑器sourcetree,就可直观的看一个commit的变化。
但用代码,能够有更丰富的细节设置,如加上一个到期的时间:
git diff master@{0} master@{1.day.ago}
具体可见git diff --help
恢复丢失的commits
Git 从不真地丢失任何东西,继续执行了历史重写操做,如rebasing, commit amending。
git log的搜索功能很强大,有丰富的设置能够查看各类状况。
例如:
分支b5有4个commit,master有1个commit超过b5
⭠ b5 ⮀ git rebase -i master //进入vi编辑器。把第4行的pick改为squash, :wq。 //terminal: [detached HEAD 55ce6f9] change one Date: Sun Nov 25 17:06:31 2018 +0800 2 files changed, 3 deletions(-) Successfully rebased and updated refs/heads/b5.
结果附加到master上的commit只有3个,最后一个是合并的commit
git log --pretty=oneline //也只能看到新增的3行log 55ce6f9c6f60d80f9b042f5f8a99556333d5854a change one 23bd2a5ead2b5a19d6bcc8667fbd23c636135264 change sth c4e32fb82669aa16ac60e2b9f8a1bc488a366be5 change bj
彷佛b5分支的最后2个commit,因为squash,致使没法找到了!其实否则:
使用git reflog命令,便可看到最新的ref日志⬇️:
55ce6f9 HEAD@{0}: rebase -i (finish): returning to refs/heads/b5 55ce6f9 HEAD@{1}: rebase -i (squash): change one 07e7fbf HEAD@{2}: rebase -i (pick): 1 23bd2a5 HEAD@{3}: rebase -i (pick): change sth c4e32fb HEAD@{4}: rebase -i (pick): change bj eb80dcf HEAD@{5}: rebase -i (start): checkout master 1c226ea HEAD@{6}: checkout: moving from master to b5
能够看到从start到finish的所有细节:一部了然!!
- 4行绿色是4个commit点
- 第2行操做的方式是squash。
若是想要恢复到执行git rebase以前,可使用:
git reset HEAD@{6}
保存改变的5个命令:
-
git add
-
git commit
-
git diff (比较commit的不一样,能够用可视化工具:sourcetree, 或者上远程仓库,在网页上看。)
-
git stash(上面已经介绍,把staged和未staged的文件储存起来)
-
.gitignore :这里就介绍它。
.gitignore
Git从copy的行为上分类: 把文件分红3种类别:
- tracked -一个文件以前被staged or commited
- untracked -一个从未被staged or commited的文件,通常是新建的文件。
- ignored -不加入tracked的文件。
包括:
- 独立缓存/packages
- build output directories:如 /bin, /out, /target
- compiled code: .pyc, .class文件
- 在运行时产生的文件: .log, .lock, .tmp
- 隐藏的系统文件: .DS_store, Thumbs.db
- 我的的IDE配置文件
通常能够在~/.gitignore文件内查看仓库中被忽略的文件:
当你有新的文件须要被忽略,.gitignore文件必须手动编辑和提交。
Global Git ignore rules
你须要本身创建.gitignore, 并设置core.excludesFile属性。
$ touch ~/.gitignore $ git config --global core.excludesFile ~/.gitignore
Shared .gitignore files in your repository
一般,Git ignore rules被定义在仓库根目录的.gitignore文件中。
但也能够在你的仓库中的不一样的目录中定义多个.gitignore文件。
然而最简单的方法仍是在根目录建立.gitignor文件,由于它自己也是被版本控制的,当你push,就能够和团队共享。
Personal Git igonore rules
你也能够定义我的的ignore模式,这在特殊的.git/info/exclude文件中。
它不会被版本控制!因此你能够作一些私事!
如何忽略一个以前commit过的文件?
从仓库删除这个文件,而后增长一个.gitignore rule.
使用 --cached选项和git rm
git-rm - Remove files from the working tree and from the index. --cached: unstage and remove paths only from the index. 但会保留在working directory!
例子:
$ echo debug.log >> .gitignore $ git rm --cached debug.log //提示:rm 'debug.log' $ git commit -m "Start ignoring debug.log"
[master 6d51c73] Start ignoring debug.log
1 file changed, 1 deletion(-)
delete mode 100644 debug.log
.gitignore自身是被追踪的!,须要git add .gitignore
Committing an ignored file
能够强制一个被忽略的文件被提交到仓库,使用-f选项便可:
$ cat .gitignore *.log $ git add -f debug.log $ git commit -m "Force adding debug.log"
固然,这不是一个明显的,可让团队成员了解的方法:改成:
$ echo !debug.log >> .gitignore $ cat .gitignore *.log !debug.log $ git add debug.log $ git commit -m "Adding debug.log"
*.log表示全部带.log后缀的文件都会被忽略,可是!dubug.log表示这个文件不会被忽略!
Stashing an ignored file
使用git stash回你历史的存储本地的变化,并在以后用git stash pop取回。
可是默认git stash忽略.gitignore中的文件。
使用--all选项,能够stash 忽略的和未跟踪的文件。
具体git stash 说明文档见:https://www.atlassian.com/git/tutorials/git-stash/#stashing-untracked-or-ignored
Debugging .gitignore files
若是你有复杂的.gitignore模式或者有多个.gitignore文件,那么想要知道一个文件为什么被忽略,就比较麻烦。
你可使用git check-ignore -v <file-name>来看什么模式让这个文件被忽略
git rm
用于把一个文件从a Git repository中移除(working directory和index, 或者只从index中移除)。
某种程度上能够认为是git add命令的相反命令。
git rm命令能够用于移除单独的文件或一组文件集合。
它的主要功能是从Git index中移除跟踪的文件。
另外它也能同时从staging index和 working directory移除文件。
注意git rm 不会移除branches。
选项--cached:
这个选项的用途是取消文件的tracking,但保留这个文件在working directory!
案例见上.gitignore
如何undo git rm?
git rm命令须要执行git commit来生效。
git rm将更新staging index 和 working directory,
⚠️这些变化不会立刻产生效果,只有当新的commit被建立,这些变化才会被添加到commit history中。
这就意味着git rm能够恢复。(符合git 做为时光机的原则)执行git reset HEAD
⚠️git rm只能用在当前current branch的文件!
为何使用git rm来代替rm
当一个被跟踪的文件被rm命令执行,Git仓库会识别这个标准的壳命令rm。
Git仓库会更新working directory来反应这个移除。但它不会更新staging index。
因此须要额外的git add命令,把这个移除的改变添加到staging index。
git rm 是一个方便的shorcut。它将同时更新working directory and the staging index 。
例子:取消文件shanghai.txt的跟踪,并删除这个文件。
⭠ master ⮀ git rm shanghai.txt 提示:rm 'shanghai.txt'
⭠ master± ⮀ git status 提示: On branch master Your branch is ahead of 'origin/master' by 4 commits. (use "git push" to publish your local commits) Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: shanghai.txt #帮你使用了git add命令。
⚠️sourcetree,可使用discard,取消一个文件的修改或删除。就是恢复操做!
等同命令:
git checkout <file>
git checkout -- <file> #加上-- 防止文件名和branch名字重复致使❌。
Unstage a file:
git reset HEAD <file>
git rm --cached <file> #删除staging保留working direotry中的文件。
小节:
git rm --cache <file>用于再也不对某个文件进行tracking。以前commi过的文件,再也不跟踪!
git clean -df 删除未跟踪的文件和目录
git reset --hard 回滚跟踪的文件,即恢复文件到未修改的状态并再也不跟踪,新增的文件则从工做目录删除。
git fetch 命令是如何与remote branches 合做的?(深刻分析)
Behind the scenes,在幕后, ./.git/objects目录中,Git存储了全部的commits, 包括local和remote。
~/practice/.git/objects ⮀ ⭠ master ⮀ ls 00 0d 1b 24 38 40 51 60 70 7e 8b 9b a8 bb c7 d4 e7 fd 02 11 1c 27 3b 43 55 65 76 81 90 9d ab bd c8 de eb info 04 15 1f 2d 3c 49 59 68 77 84 92 a1 ad be ce e0 f0 pack
经过使用branch refs(就是commit点的🆔), GIT让分支commits清楚的分开。
远程分支的Refs, 储存在./.git/refs/remotes/
//👇两条命令均可以查看 git branch -r git branch --all
本地分支的Refs,储存在./.git/refs/heads/
~/practice/.git/refs/heads ⮀ ⭠ master ⮀ ls hotfix master try
less try能够看到:
ad64e76234f1dee8ea19618d4f5964edbc20bd36 try (END)
git log
针对commit点的查询和搜索功能:
//在屏幕上显示几行 git log -n <limit> //单行显示,用于全局的概览, 内容包括id和messages git log --oneline
// 显示每一个commit的文件改变的概览 git log --stat //使用-p选项,看每一个提交点的patch。比--stat更详细的每一个commit的区别 git log -p
经过对message进行匹配来搜索,<pattern>能够是字符串和正则表达式:
git log --grep="<pattern>"
看一个特定文件的提交历史:
git log <file>
显示一个相似sourcetree的结构:
git log --graph --decorate --oneline
⚠️,选项能够一块儿使用。
git blame
- The high-level function of
git blame
is the display of author metadata attached to specific committed lines in a file. This is used to explore the history of specific code and answer questions about what, how, and why the code was added to a repository.
用于比较commit点的历史代码。信息还包括添加代码的做者名字,解释。
合做者能够查看这些历史代码。
通常使用UGI网页,如在线git上查看这些历史代码。
例子:example
git clone https://kevzettler@bitbucket.org/kevzettler/git-blame-example.git cd git-blame-example
//使用git log查看全部commit信息。
git log
git blame只用于单独的文件的历史commit点的比较。须要提供file-path来输出信息。
//输出帮助信息 git blame //输出文件的commit历史: 即每一个commit的代码的增减。 git blame README.md