GIT详解

Git 工作区、暂存区和版本库

工作区:就是你在电脑里能看到的目录。
暂存区:英文叫stage, 或index。一般存放在 “.git目录下” 下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:
GIT详解_第1张图片
图中左侧为工作区,右侧为版本库。在版本库中标记为 “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 工作流程

一般工作流程如下:
克隆 Git 资源作为工作目录。
在克隆的资源上添加或修改文件。
如果其他人修改了,你可以更新资源。
在提交前查看修改。
提交修改。
在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。
下图展示了 Git 的工作流程:
GIT详解_第2张图片

GIT基础命令操作

git init			# 初始化git管理仓库,会出现.git文件夹
git clone
git clone ssh://git.com/cloud-codec-qa/cloud-code-diff-test.git
git clone ssh://git.com/cloud-codec-qa/cloud-code-diff-test.git new_name # 使用新的命名

设定user的name和mail信息
/etc/gitconfig,操作系统:

git config --system user.name "张三"
git config --system user.email "[email protected]"
git config --unset user.name
git config --unset user.email

~/.gitconfig,针对用户:

git config --global user.name "张三"
git config --global user.email "[email protected]"
git config --global alias.br branch	# 别名设置
git config --global alias.ui '!gitk'		# 感叹号表示需要

.git/config,针对特定项目:

git config --local user.name "张三"
git config --local user.email "[email protected]"
git config --list
.gitignore
*.a 			# 忽略所有.a结尾的文件
!lib.a 			# 但lib.a除外
/TODO 			# 仅仅忽略项目根目录下的TODO文件,不包含subdir/TODO
build/ 			# 忽略build/目录下的所有文件
doc/*.txt		# 会忽略doc/notest.txt但不包含 doc/server/arch.txt
doc/*/*.txt
doc/**/*.txt
/*/test3.txt	# 忽略一级目录下的test3.txt文件
/**/test3.txt	# 忽略多级目录下的test3.txt文件

版本管理

git add # 从修改状态添加到暂存区
git add test.txt
git add .			# 提交当前目录下的修改,如果.gitignore匹配,则不加入到暂存区
git add *			# 不管.gitignore是否匹配,均增加到暂存区
git add --all		# 提交所有修改到暂存区
git add foo.txt		# 提交单个文件到暂存区
git add dir/		# 提交指定某一包含新增文件与被修改文件的目录	
git commit # 暂存区到版本库
git commit -m 'second'
git commit -amend "正确的信息"		# 与上一次是一次提交,只能修改最后一次提交
git commit -am 'my logging' 		# 等价于git add + git commit,不会提交新增文件
git rm # 从暂存区到修改状态
git rm --cached test.txt 		# 删除了一个文件,并且未被追踪,可以通过reset+checkout恢复
rm								# linux命令删除,并未纳入stage区,只需要reset即可
git rm -f hello.txt				# 工作区中的文件被修改后的删除方式
git mv (mv命令也可以实现)
git mv test1.txt test2.txt		# 等价于mv test1.txt test2.txt + git rm test1.txt + git add test2.txt

get checkout -- file.txt		# 修改工作区文件与暂存区对齐
git reset HEAD test.txt	# 将添加到暂存区的内容从暂存区移动到工作区

查看信息

git help
git help config
git config --help
man git-config
git log # 显示项目历史,所有提交都会按照时间顺序被降序排列出来
git log -n 3			# 显示近三次的提交
git log --pretty=oneline 或 git log --oneline	# 显示概述信息
git log --pretty=format:""
git log --format=fuller	# 显示许多细节信息
git log -stat -l		# 查看提交中包含的重要信息
git log -stat			# 显示统计
git log --merges	查看连个父级提交
git log --graph			# 日志图形化
git log --graph --abbrev-commit
git log --graph --pretty-oneline --abbrev-commit
git log origin/master	# 三种写法等价,最后都转换成refs/remotes/origin/master执行
git log remotes/origin/master		
git log refs/remotes/origin/master
git blame test.txt				# 查看test.txt文件所有的修改
git status
git status				# 显示出该项目自上次提交以来所有发生的所有修改
git status --short		# 相关输出更紧凑

差异比较 git diff

diff 当前文件a 目标文件b		# 列出当前文件a同目标文件b的区别
git diff 					# 比较暂存区与工作区的文件差异
git diff HEAD				# 比较最新的提交与工作区之前的差别
git diff --cached			# 比较最新的提交与暂存区的之间的差别
git diff --cached commit_id	# 比较某次提交与暂存区之间的差别
git diff temp				# 查看temp分支与本地原有分支的不同

git diff foo.txt				显示每个被修改的行
git diff --staged				显示当前版本库的HEAD提交与暂存区之间的不同(通过--staged显示暂存区内容)
git diff    77d231f    HEAD		两次提交之间的比较;在不用提交散列值的情况下,靠指定相关特定的符号名称(例如分支、标签、HEAD等)也能够获取到
git diff    77231f^!			与上一次提交进行比较;使用^!
git diff    77231f     05bcfd1  -  book/bisection		限制只显示某些文件或目录之间的差异
git diff --stat 77d231f 05cfd1	使用--stat选项显示每个文件中的修改数量

git diff --word-diff			按照单词显示我们所做的修改
git diff --word-diff=color		以不同的颜色来显示文件中的不同

远程操作(远程仓库)

git pull
拉取,同时会合并,等价于 pull == fetch + merge
git pull origin develop:develop
git push
git push origin src:dest			# 完整写法
git push origin :develop			# 将本地空分支推动到远程仓库,则远程分支被删除
git push origin --delete develop	# 同上一条命令,删除远程仓库分支
git push --set-sream origin develop	# 在远程上创建与本地相同的分支
git push --set-sream origin develop:develop2	# 远程分支名字为develop2
git push origin HEAD:develop2		# 同上一条命令
git push -u origin test				# 将本地分支推送到远程仓库并创建新的分支
git remote
git remote add origin http://github.com/myname/git_project.git	# 添加远程仓库信息
git remote -v				# 查看远程分支
git remote show				# 查看远程分支名称
git remote show origin		# 远程仓库的详细信息
git remote prune origin		# 远程分支被删除之后,裁剪本地分支(stale状态)
git remote rename origin origin2	# 将远程仓库重命名
git remote rm origin		# 删除远程仓库
git remote add origin [email protected]/git-test.git	# 上一条删除后,重新加回来远程仓库
git config --global push.default simple
git fetch					# 将远程仓库拉取到本地远程仓库
git fetch origin master:refs/remotes/origin/mymaster	
git fetch origin master:refs/remotes/origin/mymaster topic:refs/remotes/origin/topic	# 一次性拉取多个分支
删除远程分支:先删除远程分支,再从本地推送到远程分支

版本分支

HEAD 指向当前分支,master指向提交
Fast-Foward master只是指向新的提交,并没有更改

git branch
git branch temp				# 新建一个temp分支
git branch -v
git branch newcommit 1e4q5r	#基于 1e4q5r分支建立名字未newcommit的branch
git branch -a				# 查看本地分支和远程分支
git branch -av				# 查看本地分支和远程分支,带上最后一次提交的节点信息
git branch --unset-upsream		 
git branch -d temp			# 删除temp分支
git checkout
git checkout new_name			# 为某个提交起一个有意义的名字,这就是所谓的标记
git checkout -					# 两个目录来回切换
git checkout -d new_name		# 没有文件改动可直接删除,有改动的话提示不可删除
git checkout -D new_name		# 无论有无改动,直接删除该分支
git checkout -b new_branch		# 等价于创建分支并且切换到该分支
git checkout -b develop origin/develop	# 将远程仓库develop的代码拉到本地远程分支
git checkout --track origin/test	# 等价于上一条命令
git checkout . 					#本地所有修改的。没有的提交的,都返回到原来的状态
git merge
git merge new_branch			# 将new_branch合并到当前分支
git merge --no-ff dev			# 如果可能,合并分支时GIT会使用fast-forward模式,在这种情况下,删除分支会丢掉分支信息,合并时加上--no-ff参数会禁用fast-forward模式,这样会多出一个commit id
$ git checkout -b iss53
这相当于执行下面这两条命令:
$ git branch iss53
$ git checkout iss53

切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做 stashing 和 commit amending)。

分支:branch命令只能用于创建新的分支,但不会自动切换到新的分支,如果想切换用checkout
git branch 列出所有分支

创建分支
git branch a-branch						# 为当前提交创建分支
git branch still-a-branch 38b7da45e		# 为任意一批提交创建分支
git branch still-a-branch older-branch	# 从现有分支中创建分支
删除分支
git branch -d b-branch				# 删除一个已经被终止的分支
git branch -D b-branch				# 删除打开的分支(删除一个分支时还未转移到其他分支)
恢复分支
git branch a-branch 742dcf6			# (已知提交散列值)恢复某个分支
git relog							# 用reflog命令将想要恢复分支提交的散列值找到
git branch b-branch HEAD@{1}		# (通过reflog命令找到散列值)恢复该分支
切换分支
git checkout a-branch					# 切换到a-branch分支
git chechkout -b a-branch				# 创建并切换到新的分支a-branch,=创建+切换
git checkout --force a-branch			# 强制切换
git stash & git checkout a-branch		# 储存修改并切换

版本回退

git reset --hard HEAD^		# 回到上一个提交
git reset --hard HEAD^^		# 回到前两个提交
git reset --hard HEAD~1
git reset --hard commit_id
git reflog					# 操作日志
git reset HEAD test.txt		# 将文件移除暂存区
git checkout commit_id		# detached的信息

保存工作现场

git stash						# 将工作区和暂存区中修改保存在一个储存栈的缓存区
git stash list					# 查看储存栈中的内容
git stash pop					# 将最后一次stash内容弹出并删除该条记录
git stash apply					# stash内容不删除,需通过git stash drop stash@{0}手动删除
git stash drop stash@{0}		# 删除stash中的日志信息
git stash applay stash@{0}		# 直接弹出某次的stash

GIT标签

(git标签与分支无关,一经建立则就在git中存在)

git tag									# 查看所有标签
git tag v1.0							# 轻量级标签,不会创建tag对象
git tag -a v2.0 -m 'release version'	# 带有附注标签,会创建tag对象,对象的内容指向commitID
git tag -d tag_name						# 删除本地分支
git tag -l 'v1.0'
git tag -l 'v*'
将本地标签推动到远程
git push origin v7.0						# 推送v7.0到远程,简写
git push origin refs/tags/v7.0:refs/tags/v7.0	# 完整写法,源:目的地
删除标签
git push origin refs/tags/v7.0 	# 将空的标签推送到远程,即删除
git push origin --delete tag v5.0	# 删除远程标签
git fetch origin tag v7.0			# 仅拉取7.0的标签
git checkout v1.0

在缺省的情况下,refspec会被git remote add命令自动生成,git会获取远端上的refs/heads下的所有引用,并将它们写到本地的refs/remotes/origin目录下,所以,远端上有一个master分支,在本地可以通过下面几种方式访问它们的历史记录:

git log origin/master				# 三种写法等价,最后都转换成refs/remotes/origin/master执行
git log remotes/origin/master		
git log refs/remotes/origin/master	# 最完整的写法

标签有两个ID,一个是提交的ID,一个是他指向的commitID

GIT GC

将.git/refs中的信息打包成.git/pack-refs,新增加的分支会在.git/refs中,可以重新调用git gc再次加入到.git/pack-refs中

GIT裸库和submodule

git init --bare		# 创建个裸库,没有工作区的,存放开发者所存放的代码,一般在服务端
git仓库中存在依赖的git仓库
git submodule add [email protected]/gitlecture/git-child.git mymodule	# mymodule事先不能存在,将整个git-child仓库的数据创建到mymodule中,同时生成.gitmodules

git submodule foreach git pull origin master

父项目和子项目一起克隆

git clone [email protected]/gitlecture/git-parent.git git_parent --recusive
--recusive等价于下面三句,同步clone git_child的git库
git clone 父项目
git submodule init
git submodule --update --recursive

删除子项目

git rm -cached mymodule		# 从暂存区移除
rm -rf mymodule				# 从工作区移除
rm -rf .gitmymodule			# 移除配置文件

GIT subtree

相同点:与git submodule作用同,官方推荐使用subtree;
不同点:1、submodule是一个子库的引用,而subtree是在父库里面创建子库所有文件,如果父项目对子项目有所更改,会推送到远端父项目的子项目,若此时没有同步父项目中子项目信息到子库,则子库不发生改变;
三种写法一样:

git subtree add --prefix=subtree
git subtree add --prefix subtree
git subtree add -P subtree

–squash,合并,将subtree多个提交(多个commitID)合并成一个提交(重新生成一个commitID),防止子仓库多个提交污染父仓库的提交历史,如果一开始使用 --squash参数就在后面一直使用,如果一开始不使用,则后面就一直别使用;否则会有其他奇怪的错误出现。

使用 --squash

git remote add subtree-origin [email protected]/gitlecture/git-child.git	# 添加子库的远程信息

会产生两次提交,第一次是子仓库的多次commit合并成一个commit,第二次merge产生一个新commit,所以子库的更新都会生产两个commitID

git subtree add --prefix=subtree subtree-origin master --squash
git subtree pull --prefix=subtree subtree-origin master --squash
git subtree push --prefix=subtree subtree-origin master			# 将本地的更改推送到远程子分支
git subtree push --prefix=subtree subtree-origin master -d		#  显示其他的提示信息

不使用 --squash

git remote add subtree-origin [email protected]/gitlecture/git-child2.git	# 所有的子仓库提交都会放到父仓库中,不会存在合并
git 
git subtree pull --prefix=subtree subtree-origin master
git subtree push --prefix=subtree subtree-origin master

$ git subtree

usage: git subtree add   --prefix= 
   or: git subtree add   --prefix=  
   or: git subtree merge --prefix= 
   or: git subtree pull  --prefix=  
   or: git subtree push  --prefix=  
   or: git subtree split --prefix= 		# 将一个模块独立出去

    -h, --help            show the help
    -q                    quiet
    -d                    show debug messages
    -P, --prefix ...      the name of the subdir to split out
    -m, --message ...     use the given message as the commit message for the merge commit

 1. List item
 
options for 'split'
    --annotate ...        add a prefix to commit message of new commits
    -b, --branch ...      create a new branch from the split subtree
    --ignore-joins        ignore prior --rejoin commits
    --onto ...            try connecting new tree to an existing one
    --rejoin              merge the new branch back into HEAD

options for 'add', 'merge', and 'pull'
    --squash              merge subtree changes as a single commit

GIT cherry-pick

把一个分支中的修改,应用到另外一个分支上;主要应用在本地分支中

git cherry-pick commitID		# 跳过commitID之后会存在冲突,出现冲突后,手动解决;

develop恢复原始状态:

git checkout 第一次的commitID	# 此时会有一个游离状态的分支出现
git delete -D develop			# 删除原有develop分支
git checkout -d develop			# 命名当前分支为develop

GIT rebase

将一个分支修改应用到另外一个分支上,将分叉改为直线,rebase会修改提交历史

git checkout mywork
git rebase origin		# 到test分支,rebase origin,将mywork上的节点当做补丁
git rebase --continue
git rebase --skip		# 如果产生冲突,则会以origin的修改为主,丢弃mywork的补丁修改
git rebase --abort		# rebase过程中,随时执行abort,恢复到最开始的内容

git checkout test + git merge develop  =  git checkout test + git rebase develop

不要对master分支执行rebase,否则会引起很多问题;
一般来说,执行rebase的分支都是自己的本地分支,没有推送到远程版本库

rebase也会出现冲突
解决冲突之后,使用git add添加,然后执行git rebase --continue,接下来Git会继续剩余补丁
任何时候都可以通过git rebase --abort终止rebase,分支会恢复到rebase开始前的状态

rebase和merge的区别
GIT详解_第3张图片GIT详解_第4张图片

git项目中.git目录下的config文件

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
[remote "origin"]
    url = ssh://[email protected]/XXXX/test.git
    fetch = +refs/heads/*:refs/remotes/origin/*			# +,即使会有冲突依旧会将远程代码拉下来
[branch "master"]
    remote = origin
    merge = refs/heads/master
[branch "dev-q1"]
    remote = origin
    merge = refs/heads/dev-q1
[branch "dev-now"]
    remote = origin
    merge = refs/heads/dev-now
[user]
    name = 张三
    email = [email protected]

Git遵循三方合并原则
三方合并需要找到共同祖先,不断追溯下去,两条线是并行的,故合并时会遇到冲突;
GIT详解_第5张图片

创建GIT项目时指导文件

Git global setup

git config --global user.name "张三"
git config --global user.email "[email protected]"

Create a new repository

git clone ssh://github.com/git-test/t-diff-analysis.git
cd t-diff-analysis
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master

Existing folder

cd existing_folder
git init
git remote add origin ssh://github.com/git-test/t-diff-analysis.git
git add .
git commit -m "Initial commit"
git push -u origin master

Existing Git repository

cd existing_repo
git remote add origin ssh://github.com/git-test/t-diff-analysis.git
git push -u origin --all
git push -u origin --tags

你可能感兴趣的:(Git,git,subtree,rebase,git,tag,git详解)