网上的教程一般是一个一个命令的去讲解,面面俱到但是很难分辩出应该在什么情况下是用什么命令;所以这个笔记里将记录一些针对不同场合的git用法。
在开始讲解命令之前,需要对git的原理有一个理解,首先看一下这个图:
在git中我们可以认为有三个仓库(staging area,local repository和remote repository)。从本地上传到远端的服务器git(比如github或gitlab)上的做法首先是利用git add
命令将本地workspace的代码上传到一个staging area的地方,然后git commit
再上传到local repository的地方,最后再git push
到remote repository就可以了。
其中remote repository就是为了让团队协作而创建的repository,更方便的进行版本管理和开发管理。
了解了git有三个仓库之后,我觉得还需要对仓库的命名和分支命名有一些了解,首先问个问题:常用的开发中,origin和master和origin master和origin/master的区别是什么?
origin是远端仓库的名字,我们实际上可以修改这个名字:git remote rename <old-name> <new-name>
;此外,我们还可以指明多个远端仓库:
git remote add fake_test https://bitbucket.com/upstream_user/reponame.git; [remote "remote_test"]
url = https://bitbucket.com/upstream_user/reponame.git
fetch = +refs/heads/*:refs/remotes/remote_test/*
想要移除的话只需:git remote rm <name>
而master是一个分支(branch),当我们初始化git操作的时候所在的分支就是在master上;我们可以自行创建新的分支:git checkout -b [name_of_your_new_branch]
,通过git branch -a
可以查看到当前的所有分支,git branch -d [name_of_your_new_branch]
可以删除某一个分支
而在git push
命令中的用法为git push <remote-name> <branch-name>
,就是将本地的某个分支推送到远端仓库里。所以origin/master就是远端仓库名为origin的master分支。
为新仓库添加设置命令
设置为全局的git,这里所有使用git的地方使用的都是这个账户:
git config -l 查看所有设置
git config —-add —global user.name YunLambert
git config —-add —global user.email xx@xxcom
也可以设置为局部的git,只运用于当前的git目录:
git config —-add user.name YunLambert
git config —-add user.email xx@xxcom
为仓库添加remote repository
可以通过git remote -v
来查看当前的remote仓库是什么:
$ git remote -v
> origin https://github.com/YunLambert/CS-Notes.git (fetch)
> origin https://github.com/YunLambert/CS-Notes.git (push)
如果想要添加远端仓库的话,可以使用:git remote add <name> <url>
想要删除某个远端仓库:git remote rm <name>
想要重命名某个远端仓库:git remote rename <old-name> <new-name>
忽略一些文件
当使用IDE或者存储配置信息或者其他情况时可能会产生一些与代码本身无关的“中间文件”,我们并不希望将这些文件提交上去,所以可以创建一个.gitignore
文件,该文件中提到的文件名都不会被git
个人开发时常用的提交命令
个人开发时最常用的傻瓜式提交方式为:
git add .
git commit - m "..."
git push origin master
将我日常开发时涉及到的git命令流程放在下面:
1.在github上创建一个新仓库,然后通过git clone + git地址
将项目克隆到本地仓库。
2.开发后想提交时通过”傻瓜式”提交方式提交到github上的远端仓库
3.假设远端仓库有我在另外一台电脑上新的提交,则我需要先将新的代码git pull
下来。
这里简单说一下git pull,他其实是git fetch和git merge的结合。git fetch是通过远端仓库update本地仓库,git merge是合并当前仓库的change
4.如果需要转到另一个分支上进行某个功能开发,会git checkout -b [name_of_your_new_branch]
,开发完后进行it push origin :[name_of_your_new_branch]
变基操作
之前在公司实习的时候,经常用到git rebase
操作,为了就是使得将多个commit合并成一个commit后再合并到远端仓库的dev分支上,使自己的提交历史记录tidy一些。
对于merge:To be more specific, merging takes the contents of a source branch and integrates them with a target branch. In this process, only the target branch is changed. The source branch history remains the same.
对于rebase:Rebase is another way to integrate changes from one branch to another. Rebase compresses all the changes into a single “patch.” Then it integrates the patch onto the target branch. Unlike merging, rebasing flattens the history because it transfers the completed work from one branch to another. In the process, unwanted history is eliminated.
在本地rebase后,push到远端仓库时需要用git push -f
操作,不过平时尽量避免git push -f的操作,或者说git push -f是一个需要谨慎的操作,它是将本地历史覆盖到远端仓库的行为。
撤销操作
当我们提交代码后发现有些地方需要撤销操作,此时需要分情况使用git命令:
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 push,把你的修改推送到远程的仓库,现在你意识到之前推送的commit中有一个有些错误,想要撤销该commit。
此时可以使用git revert <SHA>
,因为git revert会创建一个新的commit,和指定的SHA相反的推送,自动删除原来commit中加入的内容。任何从原来的commit里删除的内容都会再新的commit里被加回去,任何原来的commit中加入的内容都会在新的commit里被删除。而
还未推送到远端仓库,但是已经提交到本地仓库,想要撤回此次提交
适用场景1:你在最后一条commit消息里有一个笔误,已经执行了 git commit -m ‘Fixes bug #42’ ,但是在git push之前你意识到这个消息应该是“Fix bug #43”。
可以用git commt --ammend
或者 git commit --amend -m ‘Fixes bug #43
git commit –amend
会用一个新的commit更新并替换最近的commit,这个新的commit会把任何修改内容和上一个commit的内容结合起来。如果当前没有提出任何修改,这个操作就只会把上次的commit重写一遍。
适用场景2:你在本地提交了一下东西(还没有push),但是所有这些东西都很糟糕,你希望撤销前面的三次提交(就像它们从来没有发生过一样)。
利用命令git reset <last good SHA>
或者 git reset --hard <last good SHA>
git reset会把你的代码库历史返回到指定的SHA状态。这样就像是这些提交从来没有发生过。缺省情况下,git reset会保留工作目录。这样,提交是没有了,但是修改内容还在磁盘上。这是一种安全的选择,但通常我们会希望一步就“撤销”提交已经修改内容(这就是–hard选项的功能)。
关于git reset,有
- –soft 参数将上一次的修改放入 staging area
- –mixed 参数将上一次的修改放入 working directory
- –hard 参数直接将上一次的修改抛弃
git reset --soft HEAD^1
撤回了本地的几次提交,想要恢复撤回操作之前的代码
适用场景:你提交了几个commit,然后用git reset –hard撤销了这些修改(见上一段),接着你又意识到:你希望还原这些修改!
利用git reflog
和git reset
或git checkout
git reflog对于恢复项目历史是一个超棒的方式。你可以恢复几乎任何(commit过的)东西。
你可以能熟悉git log命令,它会显示commit的列表。 git reflog也是类似的,不过它显示的是一个HEAD发生改变的时间列表。
一些注意事项: 它涉及的只是HEAD的改变。在你切换分支、用git commit进行提交、以及用git reset撤销commit时,HEAD会发生改变,但当你使用git checkout – 撤销时,HEAD并不会发生改变。因为这些修改从来没有被提交过,因此reflog也无法帮助我们恢复它们。 git reflog不会永远保持。Git会定期清理那些“用不到的”对象。不要指望几个月前的提交还一直躺着那里。 你的reflog就是你的,只是你的,你不能用git reflog来恢复另外一个开发者没有push过的commit。
如果你希望准确的恢复项目的历史到某个时间点,用git reset --hard <SHA>
如果你希望重建工作目录里的一个或多个文件,让它们恢复到某个时间点的状态,用git checkout <SHA> -- <filename>
如果你希望把这些commit里的某个重新提交到你的代码库里,用git cherry-pick <SHA>
还未提交到本地仓库,只是提交到了staging area,想要撤回此次提交
适用场景:无意中保存了修改,然后破坏了编辑器。不过,你还没有commit这些修改。你想要恢复被修改文件里的所有内容–就像上次commit的时候一模一样。
利用命令git checkout -- <bad filename>
git checkout会把工作目录中的文件修改到Git之前记录的某个状态。你可以提供你想返回的分支或者特定的SHA,或者在缺省情况下,GIt会认为你希望checkout的是HEAD,当前checkout分支的最后一次commit。
记住: 你用这种方法“撤销”的任何修改真的会完全消失。因为它们从来没有被提交过,所以之后Git也无法帮助我们恢复它们。你一定要确保自己了解在这个操作中丢掉的东西是什么?(也行可以利用git diff先确认一下)
将一个分支里的内容移动到另一个分支里
你进行了一些提交,然后意识到你开始checkout的是master分支。希望这些提交进入到另外一个特性(feature)分支。
使用下面的命令:
git branch feature
git reset --hard origin/master
git checkout feature
用 git checkout -b <name>
创建一个新的分支(这是创建新分支并马上checkout的流行捷径),但是你不希望马上切换分支。这里,git branch feature创建了一个叫做feature的新分支,并指向你最近的commit,但是还是让你checkout在master分支上。
下一步,在提交任何新的commit之前,用git reset --hard
把master分支倒回origin/master。不过别担心,那些commit还在feature分支里。
最后,用git checkout切换到新的feature分支,并让你最近所有的工作成果都完好无损。
大量的撤销与恢复操作
你向某个方向开始实现一个特性,但是你半路意识到另一个方案更好。你已经进行了十几次的提交,但是你现在只需要其中的一部分。你希望其他不需要的提交统统消失。
利用命令git rebase -i <carlier SHA>
,-i 参数会让rebase进入“交互模式”。它开始类似于签名讨论的rebase,但在重新进行任何提交之前,它会暂停下来并允许你详细的修改每一个提交。
squash和fixup会“向上”合并(带有这两个命令的commit会被合并到他的前一个commit里)
修复更早期的commit
你在一个更早期的commit里忘记了加入一个文件,如果更早的commit能包含这个忘记的文件就太棒了。你还没有push,但这个commit不是最近的,所以你还没法用commit –amend
方法:
git commit --squash <SHA of the earlier commit>
git rebase --autosquash -i <even earlier SHA>
git commit --squash
会创建一个新的commit,它带有一个commit消息,类似于squash! Earlier commit。(你也可以手工创建一个带有类似commit消息的commit,但是 commit –squash
可以帮你省下输入的工作)
如果你不想被提示为新合并的commit输入一条新的commit消息,你也可以利用 git commit --fixup
。在这个情况下,你很有可能会用commit --fixup
,因为你只是希望在rebase的时候,使用早期commit的commit消息。
rebase –autosquash -i 会激活一个交互式的rebase编辑器,但是编辑器打开的适合,在commit清单里任何squash!和fixup!的commit都已经配对到目标commit上了。
将一个新文件从staging area中删除
一个新文件从 staging area 中删除。并不是从硬盘上删除这个文件而是从 Git 中删除而已。加上 –cache 可以使文件只从 staging area 中移除,不会真正的删除物理文件,如果要连这个物理文件也一起删除,需使用 -f 选项
git rm --cached deleteme.rb
停止追踪一个文件
你偶然把application.log加到代码库里了,现在每次你运行应用,Git都会报告在application.log里有未提交的修改。你把 *.log放到了.gitignore文件里,可文件还是在代码库里,你怎样才能让Git“撤销”对这个文件的追踪呢?
使用命令git rm --cached application.log
虽然.gitignore会阻止Git追踪文件的修改,甚至不关注文件是否存在,但这只是针对那些以前从来没有追踪过的文件。一旦有个文件被加入并提交了,Git就会持续关注改文件的改变。类似地,如果你利用git add -f来强制或覆盖了.gitignore,Git还会持续追踪改变的情况。之后你就不必用-f来添加这个文件了。
如果你希望从Git的追踪对象中删除那个本应忽略的文件,git rm –cached会从追踪对象中删除它,但让文件在磁盘上保持原封不动。因为现在它已经被忽略了,你在git status里就不会再看见这个文件,也不会再偶然提交该文件的修改了。
临时存一下文件,不进行提交
临时提交某个文件。可以在需要临时保存修改,但又不想提交的时候使用。 git 中维护了一个栈来保存,所以支持提交多次。如果需要恢复某次提交,使用 git stash apply
。
git stash
但是这种情况下,git stash恢复后可能会出现merge conflict的情况,需要手动处理。
本地仓库中自己正在开发的分支已经落后于远端仓库,需要更新一下当前分支
你在master分支的基础上创建一个feature分支,但是master分支已经滞后于origin/master很多。现在master分支已经和origin/master同步,你希望在feature上的提交从现在开始,而不是从滞后很多的地方开始。
利用命令git checkout feature
和git rebase master
要达到这个效果,你本来可以通过git reset
(不加–hard,这样可以再磁盘上保留修改)和git checkout -b <new branch name>
然后再重新提交修改,不过这样做的话,你就失去提交历史。我们有更好的办法。
git rebase master会做下面的这些事情: 首先它会找到你当前checkout的分支和master分支的共同祖先。
然后它reset当前checkout的分支到那个共同祖先 ,在一个临时保存区存放所有之前的提交。
然后它把当前checkout的分支提到master的末尾部分,并从临时保存区重新把存放的commit提交到master分支的最后 一个commit之后。
查看三个仓库之间的差异
- git diff: 查看 working directory 与 staging area 之间的差异
- git diff –cached: 查看 repository 与 staging area 之间的差异
- git diff HEAD: 查看 working directory 与 repository 之间的差异