git init
git clone
git config [--global] user.name "Your Name"
git config [--global] user.email "[email protected]"
–global是一个可选项。如果使用了该选项,表示这台机器上所有的Git仓库都会使用这个配置。如果你希望在不同的仓库中使用不同的name和email,那么就不用global这个选项。
查看配置的命令:
git config -l
删除对应的配置的命令为:
git config [--global] --unset user.name
git config [--global] --unset user.email
.git
目录下的index文件(.git/index
)中,我们把暂存区有时也叫索引(index)。git add
命令,暂存区目录树的文件索引会被更新。git commit
的时候,master分支会做相应的更新,可以理解为暂存区的目录树才会真正写到版本库中。如果嫌输出点信息太多,看的眼花缭乱,可以试试加上--pretty=oneline
参数:
需要说明的是,我们看到的⼀⼤串类似 23807c5…56eed6 的是每次提交的 commit id (版本号),Git 的 commit id 不是1,2,3……递增的数字,⽽是⼀个 SHA1 计算出来的⼀个⾮常⼤的数字,⽤⼗六进制表⽰。
VQ1T7VH07X:git-practice bytedance$ tree .git/
.git/
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ ├── heads
│ │ └── master
│ └── remotes
│ └── origin
│ └── HEAD
├── objects
│ ├── 68
│ │ └── d4663301d2411e07243ce6ffd05c913541267c
│ ├── 83
│ │ └── b3c1c9e9a278abb737e810dcfe208295660c4f
│ ├── a8
│ │ └── 57e6ed933bee02a6889a95d6189c3ee1405497
│ ├── info
│ └── pack
│ ├── pack-42d887f3aa9c650926611499c530369ad0b83da6.idx
│ └── pack-42d887f3aa9c650926611499c530369ad0b83da6.pack
├── packed-refs
└── refs
├── heads
│ └── master
├── remotes
│ └── origin
│ └── HEAD
└── tags
19 directories, 30 files
index就是暂存区,add之后的内容添加到这里
HEAD是我们默认指向master分支到指针
默认的master分支其实就是:
打印的这一串哈希值是保存的最新的commit id
object
为Git的对象库,里面包含了创建各种版本库对象以及内容。当执行git add
命令的时候,暂存区的目录树被更新,同时工作区的文件内容被写入到对象库中的一个新的对象中,就位于.git/object
目录下,让我们来看看这些对象有何用处。
VQ1T7VH07X:git-practice bytedance$ ls .git/objects/
68 83 a8 info pack
查找Object的时候要将commit id
分成两个部分,其前2位是文件夹名称,后38位是文件名称。
找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过sha加密过的文件,好在我们可以使用git cat-file
命令来查看版本库对象的内容。
这个就是我们最近一次的提交。
其中还有一行tree 83b3c1c9e9a278abb737e810dcfe208295660c4f
,我们使用同样的方式去看看结果:
VQ1T7VH07X:git-practice bytedance$ git cat-file -p 83b3c1c9e9a278abb737e810dcfe208295660c4f
100644 blob a857e6ed933bee02a6889a95d6189c3ee1405497 README.md
我们再看README对应的a857e6ed933bee02a6889a95d6189c3ee1405497:
VQ1T7VH07X:git-practice bytedance$ git cat-file -p a857e6ed933bee02a6889a95d6189c3ee1405497
# git-practice
git实践
我是李鑫阳,我其实很喜欢你
可以看到我们对README做的修改被git记录了下来。
Git追踪的是修改,而非文件。
我们对README文件做一些修改:
我们可以使用git status
命令来查看在你上次体检之后是否对文件有进行再次修改。
上面的结果告诉我们README被修改过了,但是我们不知道具体哪些地方被修改了。因此使用git diff
命令
然后知道了哪些地方被修改了,我们再去add和commit就放心了。
之前我们也提到过,Git 能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天你发现之前前的⼯作做的出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本回退的功了。
执行git reset
命令用于版本回退,可以指定回退某一次提交的版本。回退本质是要将版本库中的内容进行回退,工作区或者暂存区是否回退要由命令参数决定。
git reset
命令语法格式为: git reset [--soft | --mixed | --hard] [HEAD]
--mixed
为默认选项,使用的时候可以不用带参数。该参数将暂存区的内容退回为指定提交版本内容,工作区内容保持不变。--soft
参数对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。--hard
参数将暂存区与工作区都退回到指定版本。切记工作区有未提交到代码时不要用这个命令,因为工作区会回滚,因为工作区会回滚,你没有提交到代码就再也找不到回来了。HEAD
说明:
可以看到version1,version2,version3的提交。如果发现version3写的有问题,想回到version2,重新基于version2进行编写。由于我们在这里希望的是将工作区的内容也回退到version2版本。我们这里希望将工作区的内容也回退到version2,因此需要用到–hard参数。
一般情况下到这里就结束了,但是如果我现在后悔了,想再回退到version3怎么办?我们还是可以继续用git reset
命令。但是我们必须要拿到version3的commit id去指定回退的版本。git log
可以找到commit id,但是我们更改了HEAD指向的最新版本之后,git log就已经无法找到version3的commit版本了,这个时候我们需要使用git reflog
命令。这个命令用来记录本地的每一次命令。
这样就可以找到所有的操作记录了,但是2a65701
这是个什么东西呢?这个是version3的commit id
部分。Git版本回退的时候也可以使用部分commit id
来代表目标版本。
这个时候git log也是已经可以看到的了。
可在实际开发中,由于长时间的开发,导致commit id找不到了,但是突然某一天我又想回退到version3,那么该如何操作呢?我告诉你,已经不可能了。
值得说的是,Git 的版本回退速度⾮常快,因为 Git 在内部有个指向当前分⽀(此处是master)的HEAD 指针, refs/heads/master ⽂件⾥保存当前 master 分⽀的最新 commit id 。当我们在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储⼀个特定的version,可以简单理解成如下⽰意图:
如果我们在工作区写了很长时间的代码,越写越写不下去,觉得自己写的太垃圾了,想恢复到上一个版本
可以使用git checkout -- [file]
命令让工作区的文件回到最近一次add或者commit的状态。
我们回顾一下git reset参数,该命令使用–mixed参数,可以将暂存区的内容退回为指定的版本内容,但工作区文件保持不变。
可以看到暂存区是干净的,工作区有修改。现在相当于没有add了,那么使用git checkout -- [file]
命令就可以了。
使用git reset --hard HEAD^
,前提是你没有把代码推送到远程仓库,如果你把代码推送到了远程仓库就真的惨了。
git rm [file]
可以把文件从暂存区和工作区中删除并且commit。
在版本回退里面,每次体检Git都把他们串成一条时间线,这条时间线可以理解成是一个分支。截止到目前为止,只有一条时间线,在Git里面,这个分支叫做主分支,即master分支。
再来理解一下HEAD,HEAD严格来说不是指向体检,而是指向master,master才指向体检,所以HEAD指向的是当前的分支。
每次提交,master分支都会向前移动一步,这样随着你不断的提交,master分支的线也会越来越长,而HEAD只要一直指向master分支即可指出当前分支。
通过查看当前的版本库,我们也可以清晰的理出思路:
VQ1T7VH07X:git-practice bytedance$ cat .git/HEAD
ref: refs/heads/master
VQ1T7VH07X:git-practice bytedance$ cat .git/refs/heads/master
d0b06aae554721d7778698681dd27d05f6417fff
发现目前dev和master都指向同一个修改。并可以验证得到HEAD目前是指向master的。
一张图总结:
我们发现HEAD已经指向了dev,就表示我们已经成功切换到了dev上面。
接下来,在dev分支下修改README文件,新增一行内容并进行一次提交操作。
这个时候图就成了这个样子了:
为了能在master主分支上看到新的提交,就需要将dev分支合并到master分支,实例如下:
git merge
命令用于合并指定分支到当前分支。合并后,master就能看到dev分支提交到内容了。此时的状态如下所示。
Fast- forward代表快进模式,也就是直接把master分支执行dev的当前提交,所以合并速度非常快。当然,也不是每一次合并都能Fast- forward。
git branch -d dev
可是,在实际分支合并的时候,并不是想合并就合成功的,有时候可能会遇到代码冲突的问题。
创建一个dev1分支,修改文件内容,然后在master分支上面也修改内容,且修改的位置有冲突。
此时master和dev1的分支有了各自的提交:
此时切换回master分支之后使用git merge dev1
。
这个时候git会告诉你已经冲突了,无法merge,并且会告你的冲突的地方是哪个文件,这个时候打开这个文件,会发现git已经标记出来了冲突的内容:
这个时候需要手动处理冲突。
解决冲突之后此时的状态变成了:
使用git log可以很清楚的看到这个问题。
git log --graph --pretty=oneline --abbrev-commit
通常合并分支的时候,如果可能,git会使用Fast forward
模式。如果我们使用Fast forward模式,那么形成的合并结果是什么呢?
在这种 Fast forward 模式下,删除分⽀后,查看分⽀历史时,会丢掉分⽀信息,看不出来最新提交到底是 merge 进来的还是正常提交的。在这种Fast forward模式下,删除分支之后,查看分支时,会丢掉分支信息,看不出来最新提交到底是merge还是正常提交的。
但是在合并冲突部分,我们看到通过解决分支冲突,我们多了一次commit,最终的状态为:
那么这个就不说fast forward模式了,这样的好处是从分支历史上可以看出分支信息。例如我们现在删除了在合并冲突部分创建的dev1分支,但依旧能看到master其实是由其他分支合并得到的。
Git支持我们强制禁用Fast forward模式。那么就会在merge的时候生成一个新的commit,这样从分支历史上就可以看到分支信息。
git merge --no-ff -m "merge with no-ff" dev2
不使用Fast forward的话是这个样子的。
master分支是非常文档的,也就是仅仅用来发布新版本,平时不能在上面干活。
平时在dev分支上面干活,dev分支是不稳定的,到某个时候,例如1.0版本发布的时候,再把dev分支合并到master上面,在master上面发布1.0版本。
因此团队合作的分支看起来其实是这个样子的:
假如我们现在正在 dev2 分⽀上进⾏开发,开发到⼀半,突然发现 master 分⽀上⾯有 bug,需要解决。在Git中,每个 bug 都可以通过⼀个新的临时分⽀来修复,修复后,合并分⽀,然后将临时分⽀删除。
可是现在dev2的代码在工作区中开发了一半,还无法提交,怎么办?
例如这样:
Git提供了git stash
命令,可以将当前的工作区信息进行储藏,被储藏的内容可以在将来某个时间恢复出来。
VQ1T7VH07X:git-practice bytedance$ git stash
Saved working directory and index state WIP on dev: 33de037 add
储藏了dev工作区之后,由于我们要给予master分支修复bug,所以需要切回master分支,再新建临时分支来修复。
hyb@139-159-150-152:~/gitcode$ git checkout master # 切回master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ git checkout -b fix_bug # 新建并切换到 fix_bug 分⽀
Switched to a new branch 'fix_bug'
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e # 修复bug--忘记写e
hyb@139-159-150-152:~/gitcode$ git add ReadMe # 重新add,commit
hyb@139-159-150-152:~/gitcode$ git commit -m"fix bug"
[fix_bug 4bbc0c4] fix bug
1 file changed, 1 insertion(+), 1 deletion(-)
修复完成,切换回master分支,并完成合并,最后删除fix_bug分支。
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ git merge --no-ff -m"merge fix_bug branch" fix_bu
Merge made by the 'recursive' strategy.
ReadMe | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
这个时候回到dev分支,工作区是干净,刚才到工作区去哪里了,用git stash list
进行查看。
使用git stash pop
恢复,恢复的同时会把stash删除。再次查看的时候已经没有现场可以恢复了。
另外,恢复现场也可以采⽤ git stash apply
恢复,但是恢复后,stash内容并不删除,你需要⽤ git stash drop
来删除;你可以多次stash,恢复的时候,先⽤ git stash list
查看,然后恢复指定的stash,⽤命令git stash apply stash@{0}
但我们注意到了,修复 bug 的内容,并没有在 dev2 上显⽰。此时的状态图为:
Master 分⽀⽬前最新的提交,是要领先于新建 dev2 时基于的 master 分⽀的提交的,所以我们在 dev2 中当然看不⻅修复 bug 的相关代码。
我们的最终⽬的是要让 master 合并 dev2 分⽀的,那么正常情况下我们切回 master 分⽀直接合并即可,但这样其实是有⼀定⻛险的。是因为在合并分⽀时可能会有冲突,⽽代码冲突需要我们⼿动解决(在 master 上解决)。我们⽆法保证对于冲突问题可以正确地⼀次性解决掉,因为在实际的项⽬中,代码冲突不只⼀两⾏那么简单,有可能⼏⼗上百⾏,甚⾄更多,解决的过程中难免⼿误出错,导致错误的代码被合并到 master 上。此时的状态为:
解决这个问题一个好的建议就是:在自己的分支上合并一下master,然后再让master合并dev。这样做的好处是有冲突的时候可以做本地分支解决并且测试,而不影响master。此时的状态为:
VQ1T7VH07X:git-practice bytedance$ git branch
* dev
master
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ git checkout master
M README.md
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 22 commits.
(use "git push" to publish your local commits)
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ git add .
VQ1T7VH07X:git-practice bytedance$ git commit -m "sst"
[master fece00e] sst
1 file changed, 4 insertions(+), 1 deletion(-)
VQ1T7VH07X:git-practice bytedance$ git checkout dev
Switched to branch 'dev'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ git add .
VQ1T7VH07X:git-practice bytedance$ git commit -m "aaa"
[dev 5b63b36] aaa
1 file changed, 1 insertion(+), 1 deletion(-)
VQ1T7VH07X:git-practice bytedance$ git merge master
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ git merge --no-ff -m "merge master branch" master
error: Merging is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm '
hint: as appropriate to mark resolution and make a commit.
fatal: Exiting because of an unresolved conflict.
VQ1T7VH07X:git-practice bytedance$ git add .
VQ1T7VH07X:git-practice bytedance$ git commit -m "sst"
[dev 6f01b8a] sst
VQ1T7VH07X:git-practice bytedance$ git merge --no-ff -m "merge master branch" master
Already up to date.
VQ1T7VH07X:git-practice bytedance$ git merge master
Already up to date.
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ vim README.md
VQ1T7VH07X:git-practice bytedance$ git add .
VQ1T7VH07X:git-practice bytedance$ git commit -m "lixinyang"
[dev e35cfcf] lixinyang
1 file changed, 1 insertion(+)
VQ1T7VH07X:git-practice bytedance$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 23 commits.
(use "git push" to publish your local commits)
VQ1T7VH07X:git-practice bytedance$ git merge dev
Updating fece00e..e35cfcf
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
当我们从远程仓库克隆后,实际上 Git 会⾃动把本地的 master 分⽀和远程的 master 分⽀对应起来,并且,远程仓库的默认名称是 origin 。在本地我们可以使⽤ git remote 命令,来查看远程库的信息,如:
VQ1T7VH07X:git-practice bytedance$ git remote
origin
VQ1T7VH07X:git-practice bytedance$ git remote -v
origin https://github.com/sjmshsh/git-practice.git (fetch)
origin https://github.com/sjmshsh/git-practice.git (push)
git push <远程主机名> <本地分⽀名>:<远程分⽀名>
# 如果本地分⽀名与远程分⽀名相同,则可以省略冒号:
git push <远程主机名> <本地分⽀名>
git pull <远程主机名> <远程分⽀名>:<本地分⽀名>
# 如果远程分⽀是与当前分⽀合并,则冒号后⾯的部分可以省略。
git pull <远程主机名> <远程分⽀名>
再来看一次git
的工作流程图,如下所示:
可以看到,git fetch
是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中
可以看到,git fetch
是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中
而git pull
则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge
,这样可能会产生冲突,需要手动解决。
在我们本地的git
文件中对应也存储了git
本地仓库分支的commit ID
和 跟踪的远程分支的commit ID
,对应文件如下:
使用 git fetch
更新代码,本地的库中master
的commitID
不变
但是与git
上面关联的那个orign/master
的commit ID
发生改变
这时候我们本地相当于存储了两个代码的版本号,我们还要通过merge
去合并这两个不同的代码版本
也就是fetch
的时候本地的master
没有变化,但是与远程仓关联的那个版本号被更新了,接下来就是在本地merge
合并这两个版本号的代码
相比之下,使用git pull
就更加简单粗暴,会将本地的代码更新至远程仓库里面最新的代码版本,如下图:
一般远端仓库里有新的内容更新,当我们需要把新内容下载的时候,就使用到git pull
或者git fetch
命令
用法如下:
git fetch <远程主机名> <远程分支名>:<本地分支名>
例如从远程的origin
仓库的master
分支下载代码到本地并新建一个temp
分支
git fetch origin master:temp
如果上述没有冒号,则表示将远程origin
仓库的master
分支拉取下来到本地当前分支
这里git fetch
不会进行合并,执行后需要手动执行git merge
合并,如下:
git merge temp
两者的用法十分相似,pull
用法如下:
git pull <远程主机名> <远程分支名>:<本地分支名>
例如将远程主机origin
的master
分支拉取过来,与本地的branchtest
分支合并,命令如下:
git pull origin master:branchtest
同样如果上述没有冒号,则表示将远程origin
仓库的master
分支拉取下来与本地当前分支合并
相同点:
不同点:
手动建立追踪关系
git branch --set-upstream-to=<远程主机名>/<远程分支名> <本地分支名>
push时建立追踪关系
git push -u <远程主机名><本地分支名>
新建分支的时候建立追踪关系
git checkout -b <本地分支名> <远程主机名>/<远程分支名>
查看追踪关系
git branch -vv
远程引用是对远程仓库的引用,包括分支,标签等等。
命名格式
master
分支的指针,并且在本地将其命名为 origin/master【远程分支 origin/master】
master
分支在指向同一个地方的本地 master
分支,这样你就有工作的基础【本地分支 master】假设指定远程仓库名字
git clone -o lixinyang
那么默认远程分支的名字就是lixinyang/master
git.ourcompany.com
的 master 分支上 push 了新的提交将本地的远程仓库和服务器上的远程仓库同步数据
git fetch <remote>
git fetch origin
git.ourcompany.com
)origin/master
指针到更新之后的位置可以看到,因为本地的 master 分支已经有过新的提交,所以和 origin/master 远程分支处于分叉状态。
现在有个新的 git 服务器位于 git.team1.ourcompany.com
当有多个远程仓库与远程分支的情况下,要怎么添加新的远程仓库引用到本地项目呢?
git remote add <remote> <git 服务器url>
抓取新添加的远程仓库在本地没有的数据
git fetch teamone
origin
服务器上的一个子集,teamone/master
指向 teamone
的 master
分支。git push <remote><branch>
将本地的serverfix分支推送到远程仓库上的awesomebranch分支
git push origin serverfix:awesomebranch
下一次其他协作者从服务器上拉取数据时,他们会在本地生成一个远程分支 origin/serverfix
,指向服务器的 serverfix
分支的引用:
$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
这样操作,本地不会自动新增一个 serverfix 分支,只是有一个不可修改的 origin/serverfix 指针
git merge origin/serverfix
这也是将 origin/serverfix 远程分支下的内容合并到本地当前所在分支
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
这样可以在本地新建一个 serverfix 分支,并且和 origin/serverfix 远程分支指向同一个提交内容