git学习笔记 - Go语言中文社区

git学习笔记


学习资料
pro git
git - 简明指南
Github官方帮助文档
Git Community Book 中文版
参考
廖雪峰GIT教程
Learn Git Branching
Git笔记(一)——[commit, checkout]
Git笔记(二)——[diff, reset]
Git笔记(三)——[cherry-pick, merge, rebase]
git flow
从0开始学习 GitHub 系列之「初识 GitHub」
windows下使用git和github建立远程仓库
团队中的 Git 实践

注意,本文不介绍基础步骤,请参考廖雪峰GIT教程

一、配置

1.设置账号信息
参考git config命令使用第一篇——介绍,基本操作,增删改查

git config --global user.name "Your Name"
git config --global user.email "email@example.com" 

使用--global参数表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

2.ssh
由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置。为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
参考
Windows 7下Git SSH 创建Key的步骤(by 星空武哥)
ssh-keygen 不是内部或外部命令
如何在TortoiseGit中使用ssh-keygen生成的key

Paste_Image.png

3.忽略一些文件参考.gitignore 文件使用说明

4.alias配置别名
参考廖雪峰Git

有没有经常敲错命令?比如git status?status这个单词真心不好记。如果敲git st就表示git status那就简单多了,当然这种偷懒的办法我们是极力赞成的。

在C:UsersCUIXU路径找到.gitconfig文件,添加alias部分

[user]
    name = Your Name
    email = email@example.com
[alias]
        st = status
        co = checkout
        ci = commit
        br = branch
        rt = remote
        last = log -1 --name-status
        al = log --pretty=format:'%h %cn -%d %s (%cd)' --abbrev-commit -30
        l = log --graph --pretty=format:'%Cred%h%Creset %Cgreen%cn -%C(cyan)%d%Creset %s %Cgreen(%cd)%Creset' --abbrev-commit -30
        ll = log --name-status --graph --pretty=format:'%Cred%h%Creset %Cgreen%cn -%C(cyan)%d%Creset %s %Cgreen(%cd)%Creset' --abbrev-commit -10
[core]
    autocrlf = true
    excludesfile = d:\Documents\gitignore_global.txt
[difftool "sourcetree"]
    cmd = 'C:/Program Files (x86)/Beyond Compare 3/BComp.exe' "$LOCAL" "$REMOTE"
[mergetool "sourcetree"]
    cmd = 'C:/Program Files (x86)/Beyond Compare 3/BComp.exe' "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
    trustExitCode = true
5.GitHub 第一坑:换行符自动转换

Git在windows平台下,为了方便统一行尾,引入了autocrlf功能。 在提交修改时,自动将文本的eol修改为LF。检出时自动修改为CRLF。 然而实际上,自动转换eol有可能会导致一些兼容性的问题。这样可以选择主动关闭autocrlf功能:git config --global core.autocrlf false

6.git add --all 为啥不能添加空文件夹
二、常用操作

1.git init
初始化生成.git文件夹
2.git status
当前状态

On branch develop//当前所处分支
Your branch is up-to-date with 'origin/develop'.//up to date表示跟得上潮流,即最新的
nothing to commit, working directory clean

On branch master
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)//is ahead of表示在远程版本前面,可以push一下
nothing to commit, working directory clean

On branch develop
Your branch is behind 'origin/develop' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
//is behind表示落后远程版本了,并且可以fast forwarded快速合并
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:   SXD2-Game/src/com/gamehero/sxd2/gui/endless/EndlessWindow.as


no changes added to commit (use "git add" and/or "git commit -a")

Your branch is behind 'origin/develop' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

3.git commit
git add test.txt/src文件夹 //将改动添加到暂存区,可以多次执行add命令,然后一次性commit
git add . 提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件(一般使用该方法)
git add -u 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new)
git add -A 提交所有变化,是上面两个功能的合集(git add --all的缩写)

git commit -m "本次提交的说明备注"
4.git log
git log 查看提交记录(注意如果输出的log过长,可以输入Q键来退出)
git log 文件名/文件夹 查看某个文件的提交记录
git log -1 查看最近1次的提交日志 可以通过此命令看到当前所处版本的hash值及其它信息
git log --author="<pattern>" 根据提交作者,搜索提交历史 pattern 可以是字符串或正则表达式
git log --pretty=oneline 此参数可以将日志简略为版本号加提交说明
git shortlog 查看每个人的提交记录
git reflog 可以查看所有分支的所有操作记录(包括(包括commit和reset的操作),包括已经被删除的commit记录,git log则不能察看已经删除了的commit记录

三、分支

1.创建分支
master发布稳定版本 只在发布时使用
dev为开发分支 内部使用 从dev上每个小组成员都有自己的分支 做完提交

git branch dev //创建dev分支
git checkout dev //切换分支
git checkout -b dev //也可以合并成一行:创建加切换
git checkout -b feature/endless //在可视化工具中出现文件夹方便管理
git checkout -b branch3 1a222c3//创建branch3分支,从当前分支的第1a222c3节点开始

//把远程的release下的beta1.1版本checkout到本地
$ git branch release/beta1.1 origin/release/Beta1.1
Branch release/beta1.1 set up to track remote branch release/Beta1.1 from origin.

git branch可以查看当前所有分支,其中带*号的是当前分支
git branch -r 查看所有远程分支

切换到master后,使用git merge dev相当于合并指定分支dev到当前分支master
git branch -d dev则是合并后删除掉dev分支 丢弃一个没有合并过的分支需要git branch -D dev

个人理解:master分支和dev分支是两个指针,HEAD指向哪个指针,当前代码就在哪个分支。创建新的分支dev时,只是新增加一个dev指针,然后HEAD指向dev。此后,所做的更改就是针对dev这条线移动指针;如果把HEAD切换回master会发现代码还是以前的样子。如果想把dev的覆盖上来,就使用git merge dev。合并完成后,再删除dev分支,就只剩下master了。Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

2.HEAD
你现在在哪儿,HEAD 就指向哪儿,所以 Git 才知道你在那儿!不过 HEAD 并非只能指向分支的最顶端(时间节点距今最近的那个),实际上它可以指向任何一个节点,它就是 Git 内部用来追踪当前位置的东东。有的时候HEAD会指向一个没有分支名字的修订版本,这种情况叫”detached HEAD“
比如:
git checkout HEAD~3,进入分离头状态,那么在这个时候git branch new_branch一下,在此处创建一个分支,然后再git checkout new_branch,即可切换到该分支。我感觉分离头状态也就这么用了,就是切换到某个提交处,在该处建立有名的分支。
(注:HEAD^ 表示上一个版本,如果在命令行中提示More,可以把用引号括起来,即HEAD"")

3.git checkout和git reset
参考
細說git reset和git checkout的不同之處
5.2 代码回滚:Reset、Checkout、Revert的选择

git是一个非常好用的版本控制软件,但是在使用上偶尔会碰到使用git checkout和git reset,这时候就会想说到底要用哪个才是正确的,简单的结论是,除非你清楚知道你在做什么,不然不要使用git reset,大部分的情况都可以不用使用它,它就跟Linux中的rm指令一样,不可以随便乱用,因为一旦使用了就会造成不可还原的影响。

在详细介绍之前首先必须知道什么叫做工作目录、索引和HEAD。

Paste_Image.png
  • 工作目录(Working Tree)是保存目前正在处理档案的目录,Git相关的操作都会在这个目录下完成。
  • 索引(Index)位于工作目录和数据库之间,是为了向数据库提交作准备的暂存区域,又被称为staging area。
  • HEAD是指到现在我们在操作哪一个commit,当我们checkout的时候其实就是移动HEAD就会跳到另外一个commit或branch上了,HEAD被移动的时候会造成工作目录的档案内容改变,所以如果有档案被修改过但是没有commit或stash的档案存在时会不能够移动HEAD,也就是不能使用git checkout。

git checkout

注意:git checkout可以说是身兼多职——上得了厅堂,下得了厨房。一个是分支相关的操作,另一个是可以恢复文件到之前的某个状态。

checkout的意思在字典中可以查到为to depart from a place;record one’s departure from work,也就是说这是一个切换并且记录现在的情况的指令,使用checkout的时候最重要的是不会修改master的位置,而且不会修改索引,只会更改目前目录内的数据(也就是HEAD会被修改到该commit或branch),也就是说不会破坏git所储存的管理信息,只是目前工作目录下面的档案会被替换掉而已,如下所示。
原本的master长这样子
- A - B - C (HEAD, master)
如果使用了git checkout B则会变成
- A - B(HEAD)- C(master)
如果在这时候创建新的branch,git checkout -b new-branch,则会变成

- A - B - C(master)

D(HEAD,new-branch)

基本上checkout是一个很安全的行为,常常会被用于想要取得之前commit的某个档案来比较更改之前和更改之后差在哪里,或着想要回到以前的commit,也可以跳到另外一个branch也可以开新分支.

(1)checkout是将HEAD移到一个新的分支,然后更新工作目录。因为这可能会覆盖本地的修改,Git强制你提交或者缓存工作目录中的所有更改,不然在checkout的时候这些更改都会丢失。

D:git>git checkout master
error: Your local changes to the following files would be overwritten by checkou
t:
        test.txt
Please commit your changes or stash them before you can switch branches.
Aborting

(2)git checkout HEAD~2对于快速查看项目旧版本来说非常有用。但如果你当前的HEAD没有任何分支引用,那么这会造成HEAD分离。这是非常危险的,如果你接着添加新的提交,然后切换到别的分支之后就没办法回到之前添加的这些提交。因此,在为分离的HEAD添加新的提交的时候你应该创建一个新的分支。

git checkout cc59b55提示信息

此时Git处在Detached HEAD状态,从SourceTree里也可以看出有一个HEAD的tag指向对应的commit。HEAD可以理解为时一个指针,指向当前所在的分支当前的commit。其实这个时候Git处在一个“游离的匿名分支”上,Git提示说你可以做修改,做提交,但一旦你checkout到别的地方,这些提交将无法再引用到,如果你想保存,必须在此基础上创建一个新的分支。什么意思呢?我们跟着提示一步一步做。首先给test.txt文件(此时文件只有三行,最后一行是test 2.5)再添加一行“test aaa”并提交git commit test.txt -m "commit aaa"
。这时,看一下SourceTree的状态图:

commit aaa

按照Git给的提示,此时我们checkout到别的地方去:git checkout master
切回master分支,再去SourceTree看一眼,我擦泪,刚才的修改丢了!!如下图:

commit aaa丢失

不听“提示”言,吃亏在眼前,难道真的像提示说的那样,刚才的修改再也找不到了吗?非也,只要记得commit的hash,我们是可以切回去,从图11中找到commit aaa所对应的hash,运行git checkout 6382c7d,再看看SourceTree,OK,都回来了!“游离的匿名分支”的取名就来源于此,这些commit目前不属于任何分支,不能通过切分支的方式找到他们,只能记住hash才能切回来。为了保存这些提交,我们按照提示新建一个分支:git checkout -b branch2。这样以后就可以通过git checkout branch2切到该分支找到这些提交了。

git reset
比如,下面这两条命令让hotfix分支向后回退了两个提交。

git checkout hotfix
git reset HEAD~2
git reset --hard <commit ID号>//hard模式,回退到某个版本

hotfix分支末端的两个提交现在变成了悬挂提交。也就是说,下次Git执行垃圾回收的时候,这两个提交会被删除。

Paste_Image.png

reset意义为to set again,to clear,如上图所示一旦使用git reset不论是用什么模式都会更改到master所指的commit,依照现在处于哪个branch会有不同,如果不是在master而是在test这个branch上的只会更改到test所指的commit,为求方便这边都以master为例。
如果使用git reset,基本上会是在想要更改master或索引,也就是更改git所储存的管理信息才会使用,例如有一个commit因为打错想要撤消,这时因为他已经被存入索引,必须要使用git reset来把刚刚的commit撤消掉,这个动作可以有几个选择如下所示,不管哪一种模式都会变更到master,这点要特别注意,另外如果没有加任何参数时会使用预设的mixed模式。

模式名称 master的位置 索引 工作目录
soft 修改 不修改 不修改
mixed 修改 修改 不修改
hard 修改 修改 修改

Paste_Image.png

总结如下:使用--soft就仅仅将头指针恢复,已经add的缓存以及工作空间的所有东西都不变。如果使用--mixed,就将头恢复掉,已经add的缓存也会丢失掉,工作空间的代码什么的是不变的。如果使用--hard,那么一切就全都恢复了,头变,aad的缓存消失,代码什么的也恢复到以前状态。

一旦使用了git reset,依照接下来做了什么事情而定,对不熟悉的人很可能会导致这个repository再也无法push回去git server(例如Github),因为HEAD的位置和信息和原本的不一致,导致他判断你有做过更改,所以不给push,这时候就会很麻烦了,这也是为什么尽量不要使用git reset,唯二建议使用git reset的只有两个地方,第一个是git add加到不想要的档案到索引想要取消掉,第二个是自己commit之后发现有东西想要修改或著有问题想要修改,可以使用git reset–soft HEAD^。
如下示例,原本的master长这样子
- A - B - C(HEAD,master)
如果使用了git reset B则会变成
- A - B (HEAD, master) # - C is still here, but there's no branch pointing to it anymore

如果是想要切换HEAD(工作目录的内容)或branch,请使用git checkout,想要开启新branch也是使用checkout,checkout就是一个负责移动HEAD指到不同地方的指令
如果想要清除过去做过的坏事,就使用git reset,会帮你把纪录都抹掉,消除掉,使用时请谨慎使用,reset是一个负责移动HEAD和master的指令,前者为指向当前commit,后者为历史记录的最新一笔commit,一旦master被移动了,虽然还可以找得回来被抛弃的记录,但是做这件事情的时候基本上就是想要消去记录,请谨慎使用。

当你传入HEAD以外的其他提交的时候要格外小心,因为reset操作会重写当前分支的历史。正如Rebase黄金法则所说的,在公共分支上这样做可能会引起严重的后果。

4.revert
Revert撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。比如,下面的命令会找出倒数第二个提交,然后创建一个新的提交来撤销这些更改,然后把这个提交加入项目中。

git checkout hotfix
git revert HEAD~2
git revert <commit ID号>//回退到某个版本

如下图所示:

Paste_Image.png

相比git reset,它不会改变现在的提交历史。因此,git revert可以用在公共分支上,git reset应该用在私有分支上。你也可以把git revert当作撤销已经提交的更改,而git reset HEAD用来撤销没有提交的更改。就像git checkout 一样,git revert 也有可能会重写文件。所以,Git会在你执行revert之前要求你提交或者缓存你工作目录中的更改。

5.使用上述结论来撤消对一个文件比如test.txt的修改
注意:git reset和git checkout命令也接受文件路径作为参数。git revert没有文件层面的操作。
checkout:
使用checkout的时候最重要的是不会修改master的位置,而且不会修改索引,只会更改目前目录内的数据.
reset:
使用--soft就仅仅将头指针恢复,已经add的缓存以及工作空间的所有东西都不变。如果使用--mixed,就将头恢复掉,已经add的缓存也会丢失掉,工作空间的代码什么的是不变的。如果使用--hard,那么一切就全都恢复了,头变,aad的缓存消失,代码什么的也恢复到以前状态。
状态一:如果对文件的修改没有添加到索引缓存区,运行git checkout -- test.txt,可以看到刚才的更改被取消了,也就是说我们从Repo的最新commit里将test.txt恢复到了我们的工作目录里。这里的--符号主要是为了避免歧义,其实这里我们不要--,直接运行git checkout test.txt也是可以的,但试想这么一种情况:我们的文件名称与分支名称一样,比如有一个叫做“master”的文件,如果不加--则Git会认为你想切换分支,所以必须使用--来告诉Git你想恢复文件而不是切换分支,多才多艺的人就是这样闹心啊!
状态二:如果已经添加到缓存区,checkout就不行了。这时git reset test.txt执行后,因为默认的mixed参数会把缓存区还原,但工作空间区还在,所以文件内容看起来还是修改过的,此时继续用checkout,发现checkout可以生效了,即已经回到了状态一的情况。(利用这一点可以把一个进入暂存区的文件回退到工作区,并且保留这个修改。如果不想保留这个修改,可以使用--hard一步到位,或者继续用状态一的解决方式,即checkout一次。)

D:SXD2>git reset HEAD
Unstaged changes after reset:
M       SXD2-Game/src/com/gamehero/sxd2/gui/endless/EndlessWindow.as
M       SXD2-Game/src/com/gamehero/sxd2/gui/endless/EndlessWindowMediator.as

如果使用git reset --hard test.txt,会报错fatal:Cannot do hard reset with paths.,这说明上面的git reset test.txt也相当于git reset --mixed HEAD。现在把命令改为git reset --hard HEAD,即一次性还原了。

6.注意reset和revert参数的区别
revert用在公共分支上,如果想撤消最后一次提交,需要git revert HEAD
reset用在私有分支上,如果想撤消最后一次提交,需要git reset HEAD^,因为git reset HEAD是回到最后一个版本,即参数为TO。

7.Stash功能
stash
英 [stæʃ] 美 [stæʃ]
n.隐(贮)藏物;(旧)藏身处
v.贮藏;隐藏,藏匿;〈英〉停止
参考
廖雪峰 stash
Git Stash用法

你正在开开心心的coding,东写写西改改。突然!一个人对你说道“改个BUG”。如果这个BUG和你正在搞的代码可能相关。怎么办?立马改,为自己埋了一个雷。过会改,处理不当,BUG高于需求。此场景稀松平常。BUG随时有,但你能做到随时改吗?git stash拯救你。git的强大就是让你各种切换,伸缩自如。

在下面的例子中,我本来在master分支做了一些修改,现在想stash后切换到dev分支。直接切换会error:

D:git>git checkout dev
error: Your local changes to the following files would be overwritten by checkout:test.txt
Please commit your changes or stash them before you can switch branches.
Aborting

(1)保存
使用git stash保存当前的操作,如果不这么做,你在切换到别的分支之前就一定要提交已经有的改动。但你当前的操作尚未完成,所以要暂时保存起来。

D:git>git stash
Saved working directory and index state WIP on master: c10bda7 2.0
HEAD is now at c10bda7 2.0//发现版本回退了,就像什么都没写过一样

(2)查看
直接使用git stash list就可以了。

stash@{0}: WIP on master: c10bda7 2.0

(3)恢复
用git stash pop stash@{num},num 是你要恢复的操作的序号,所以你最好在回复前用git stash list查看一下。git stash pop命令是恢复stash队列中的stash@{0},然后从记录就删除,就是常规的pop操作。
git stash apply [--index] [<stash>]除了不删除恢复的进度之外,其余和git stash pop 命令一样。

//这里我做了奇怪的事情,切换到dev分支后,发现也能看到stash的内容,apply下来发现冲突了……
D:git>git branch
  branch1.0
  bugFromDelete
  dev
* master

D:git>git checkout dev
Switched to branch 'dev'

D:git>git stash list
stash@{0}: WIP on master: c10bda7 2.0

D:git>git stash apply
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt

言归正传,我们在dev干完活了,又回到了master并想恢复现场

D:git>git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)

D:git>git stash list
stash@{0}: WIP on master: c10bda7 2.0

D:git>git stash pop
On branch master
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)
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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2eebbbc9b6e47a625e34b27ff7a29c9174d21a08)

(4)删除
当git stash pop出现冲突时,需要解决冲突,解决完用stash list发现这个stash还在,并没有pop掉。
stash存的不要过多,不然你也不知道哪个是哪个,最好随时清一清,或在用之前确认一下。把所有的记录都清空掉用git stash clear。

(5)stash -u 处理untracked文件

引自Git Stash 历险记
不知道大家看明白了没有,这个漫画是用来吐槽git stash默认只保存跟踪文件的快照,而放任其他文件(untracked files)不管的特点。如果这里在贮藏之后修改了app/views/users的内容,因为git stash之前没有保存过这个文件,用git stash pop或者git stash apply都是没办法恢复的,这种情况只能自认倒霉。想把这些untracked files的进度也保存的话,只需添加-u参数即可。

我来做一下试验,添加一个untracked.txt,然后git stash

D:git>git stash
No local changes to save

好吧,修改一下test.txt文件,继续stash发现成功了,但是git status后,发现

D:git>git stash list
stash@{0}: WIP on master: c10bda7 2.0

D:git>git status
On branch master
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        untracked.txt

nothing added to commit but untracked files present (use "git add" to track)

这个文件就被默默地扔在这了,即使切换切换到dev分支,发现它还是处于untracked状态。
之后再切回master,然后stash pop,发现它还在那。就是说,从头到尾,根本没有它的事儿。
不过为了防止在dev分支不小心把它给提交上去,而且让stash这个命令贮存得更完整,还是加上-u参数吧。

8.git cherry-pick
参考
git cherry-pick 小结
批量git-cherry-pick
git cherry-pick 最佳实践

cherry-pick可以选择某一个分支中的一个或几个commit(s)来进行操作。例如,假设我们有个稳定版本的分支,叫v2.0,另外还有个开发版本的分支v3.0,我们不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个v3.0中的功能到v2.0中,这里就可以使用cherry-pick了。
git cherry-pick 2b48938 3320829 1e09778//一个或几个commit(s)
注意:当执行完 cherry-pick 以后,将会 生成一个新的提交;这个新的提交的哈希值和原来的不同,但标识名一样;

Git从1.7.2版本开始支持批量cherry-pick,就是一次可以cherry-pick一个区间的commit。用法如下:

$ git cherry-pick <start-commit-id>..<end-commit-id>
或
$ git cherry-pick <start-commit-id>^..<end-commit-id>

下面这个场景可以使用批量cherry-pick:有两个分支,master和develop,develop有一部分功能比较紧急,需要优先上线,所以develop的一部分提交要合并到master分支。通过git-merge命令,忽略一些commit也可以临时满足要求,说“临时”是因为develop分支上剩下的提交迟早也要合到master上去,所以通过git-merge忽略分支的做法不大可行。这时候使用git-chrry-pick进行批量操作比较方便。

//master分支下只有1次提交
(master)$ git log --pretty=onelinee8759e501529b9eea04d5717aeb13e2fa83a7a2d base
//develop分支下有10次提交。
(develop)$ git log --pretty=oneline
91b8696560ce3c2fe4b282d4fa814447c0d93cd1 10
e97cc3c92634c8278e1c60b9053d735033db956a 9
58febd437b20391611f90ebeedea0d024345655f 8
562130942862e44a11009b60bb9bddbaa23dc21f 7
dd627ee763bdc3190f6ebd199f74b9a67978b30c 6
9b68ddf74e50573e4cdc6542fb17c182c12e3e07 5
b1d63e7181bb9ef996a90368a9c5a840f201ae80 4
793cd10cbab4ba147b0011d4bdf882d050ff6435 3
1739e414db9709821ae253187a7ce38c56bf6f2d 2
e8759e501529b9eea04d5717aeb13e2fa83a7a2d base
//现在要把develop分支上的3-4,7-10次提交合并到master分支,2、5和6暂时不合
(master)$ git cherry-pick 1739e4..b1d63e //(2, 4]
(master)$ git cherry-pick 562130^..91b869 //[7,10]
//检查master分支log,所需提交已经合并完成。
(master)$ git log --pretty=oneline
b195b40627f333f2e8f2fa0429d099364b50dd65 10
66ca86797023d05eda47804b325c575ee976d2f0 9
e677961d84dc77771086e3cca0b04fe35bd00046 8
e0dcac8a3786e4b96455a5d1ddac1e30631ee8af 7
87fad31564e801d143ea0a88329a174127a9b477 4
1dbd37e4728b8a92117f2070d057aaff426ce3fa 3
e8759e501529b9eea04d5717aeb13e2fa83a7a2d base

9.git diff
详情参考Git笔记(二)——[diff, reset]
Windows下使用Beyond Compare作为git的比对与合并工具
git diff 查看更改记录 比如git diff readme.txt

  • git diff source target返回的结果是target相对于source的变化,这里的source和target可以是commit的hash/分支名/快捷方式
  • 如果只给一个参数,则这个参数就是source,而默认的target是工作目录,如果工作目录clean的话,则 target为当前所在分支的最新commit
  • 如果一个参数都不给,默认的source是暂存目录,而target还是工作目录
  • 如果想要使暂存目录作为target的话,需要使用--cached参数

10.不fetch想知道远程有哪些更新

使用SourceTree时,它会自动更新,在界面上显示出当前所处的版本,以及落后了几个版本。

SourceTree

如果我们不用这个工具,而是命令行模式,怎么达到这个效果呢。参考Git如何在本地查看远端仓库超前本地提交的日志信息?仅仅是日志信息哦**,专门建立一个分支来同步远程更新……

//checkUpdate是专门检查远程更新的分支,我让它从bf58ac8开始追踪
D:SXD2>git checkout -b checkUpdate bf58ac8
Checking out files: 100% (296/296), done.
Switched to a new branch 'checkUpdate'

//从origin develop获取更新
D:SXD2>git pull origin develop
From 10.1.29.86:Client/sxd2
 * branch            develop    -> FETCH_HEAD
Updating bf58ac8..ff1cc3e
Fast-forward
...
四、远程操作

参考Git远程操作详解

Paste_Image.png

1.建立远程仓库别名并链接到远程仓库
git remote add origin git@github.com:CuiXu1987/TestGit.git
origin是远程仓库在本地别名,你可以自由取,git链接是在建立远程仓库时要你记下的ssh连接。此句就是在本地建立远程仓库别名并链接到远程仓库。

//看到每个别名的实际链接地址
D:SXD2>git remote -v
origin  git@10.1.29.86:Client/sxd2.git (fetch)
origin  git@10.1.29.86:Client/sxd2.git (push)

2.从远程库克隆

git clone git@github.com:CuiXu1987/gitskills.git 会将远程项目下载到当前路径
git checkout -b dev origin/dev
//要在dev分支上开发,就必须创建远程origin的dev分支到本地dev分支

3.git push
git push -u origin master
origin即上面的远程仓库别名,master表示要推送到哪个分支。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;

4.git fetch + git merge == git pull
在实际使用中,git fetch更安全一些 因为在merge前,我们可以查看更新情况,然后再决定是否合并
参考git fetch 的简单用法:更新远程代码到本地仓库
FETCH_HEAD指的是: 某个branch在服务器上的最新状态'.每一个执行过fetch操作的项目'都会存在一个FETCH_HEAD列表, 这个列表保存在 .git/FETCH_HEAD 文件中, 其中每一行对应于远程服务器的一个分支.当前分支指向的FETCH_HEAD, 就是这个文件第一行对应的那个分支.

一般来说, 存在两种情况:

如果没有显式的指定远程分支, 则远程分支的master将作为默认的FETCH_HEAD.
如果指定了远程分支, 就将这个远程分支作为FETCH_HEAD.
常见的git fetch 使用方式包含以下四种:

git fetch
这一步其实是执行了两个关键操作:

  • 创建并更新所有远程分支的本地远程分支.
  • 设定当前分支的FETCH_HEAD为远程服务器的master分支 (上面说的第一种情况)

需要注意的是: 和push不同, fetch会自动获取远程`新加入'的分支.

git fetch origin
同上, 只不过手动指定了remote.

git fetch origin branch1
设定当前分支的 FETCH_HEAD' 为远程服务器的branch1分支`.

注意: 在这种情况下, 不会在本地创建本地远程分支, 这是因为:

这个操作是git pull origin branch1的第一步, 而对应的pull操作,并不会在本地创建新的branch.

一个附加效果是:

这个命令可以用来测试远程主机的远程分支branch1是否存在, 如果存在, 返回0, 如果不存在, 返回128, 抛出一个异常.

git fetch origin branch1:branch2
只要明白了上面的含义, 这个就很简单了,

首先执行上面的fetch操作
使用远程branch1分支在本地创建branch2(但不会切换到该分支),
如果本地不存在branch2分支, 则会自动创建一个新的branch2分支,
如果本地存在branch2分支, 并且是`fast forward', 则自动合并两个分支, 否则, 会阻止以上操作.
git fetch origin :branch2 等价于: git fetch origin master:branch2

(1)查看远程分支

D:SXD2>git branch -r
  origin/HEAD -> origin/master
  origin/develop
  origin/feature/Redbag
  origin/feature/一键吞噬
  origin/feature/礼品铺子
  origin/master
  origin/release/Alpha8.5
  origin/release/Alpha8.6
  origin/release/Alpha8.7

(2)从远程的origin仓库的develop分支下载到本地并新建一个分支temp
默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。
$ git fetch <远程主机名> <分支名>
比如,取回origin主机的master分支。
$ git fetch origin master
所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取。比如origin主机的master,就要用origin/master读取。可以在它的基础上,使用git checkout命令创建一个新的分支。
$ git checkout -b newBrach origin/master
也可以合并到一起处理:

D:SXD2>git fetch origin develop:temp
From 10.1.29.86:Client/sxd2
 * [new branch]      develop      ->temp

(3)比较当前分支和temp分支的不同

$ git diff temp
……//内容太多,可以使用sourceTree工具查看

(4)合并temp分支到develop分支,有可能会出现冲突

D:SXD2>git merge temp
Updating 4d1990c..b606f99
Fast-forward
 SXD2-Game/src/com/gamehero/sxd2/core/URI.as        |   4 +-
 .../sxd2/gui/otherPlayer/OtherHeroPanel.as         | 110 ++++----
 .../gamehero/sxd2/gui/player/hero/HeroHeadPanel.as |   2 +-
 .../gamehero/sxd2/gui/player/hero/HeroWindow.as    |  32 ++++--
 .../com/gamehero/sxd2/manager/FunctionManager.as   |   4 +-
 .../com/gamehero/sxd2/manager/ScheduleManager.as   |  12 +--
 SXD2-Game/src/com/gamehero/sxd2/vo/ScheduleVO.as   |   2 +-
 7 files changed, 58 insertions(+), 108 deletions(-)

(5)如果不想要temp分支了,可以删除此分支

D:SXD2>git branch -d temp
Deleted branch temp (was b606f99).

如果该分支没有合并到主分支会报错,可以用git branch -D <分支名>强制删除
(6)git pull

D:SXD2>git pull origin develop
From 10.1.29.86:Client/sxd2
 * branch            develop    -> FETCH_HEAD
Updating b606f99..99f8230
Fast-forward
 SXD2-Game/src/com/gamehero/sxd2/gui/WindowCommand.as                | 3 ++-
 .../src/com/gamehero/sxd2/gui/blackMarket/mystery/MysteryShop.as    | 6 +++
 SXD2-Game/src/com/gamehero/sxd2/gui/giftStore/GiftStoreWindow.as    | 2 +-
 SXD2-Game/src/com/gamehero/sxd2/gui/giftStore/model/GiftStoreVo.as  | 4 +++
 SXD2-Game/src/com/gamehero/sxd2/manager/GiftStoreManager.as         | 4 +++
 5 files changed, 14 insertions(+), 5 deletions(-)

5.git merge 与 git rebase

rebase意味着改写了history,你还是要不停的叮嘱新人不要rebase了remote branch

参考闲谈 git merge 与 git rebase 的区别
(1)merge
现在假设我们有一个主分支 master 及一个开发分支 deve,仓库历史就像这样:

初始仓库历史

现在如果在 master 分支上git merge deve:
Git 会自动根据两个分支的共同祖先即e381a81这个 commit 和两个分支的最新提交即8ab7cff696398a进行一个三方合并,然后将合并中修改的内容生成一个新的 commit,即下图的78941cb

merge 合并图

(2)rebase
rebase 是什么情况呢?还是一个初始的仓库历史图:

ebase初始仓库历史

如果是在 master 分支上 git rebase deve
Git 会从两个分支的共同祖先 3311ba0 开始提取 master 分支(当前所在分支)上的修改,即 85841bea016f64e53ec51,再将 master 分支指向 deve 的最新提交(目标分支)即 35b6708 处,然后将刚刚提取的修改依次应用到这个最新提交后面。操作会舍弃 master 分支上提取的 commit,同时不会像 merge 一样生成一个合并修改内容的 commit,相当于把 master 分支(当前所在分支)上的修改在 deve 分支(目标分支)上原样复制了一遍,操作完成后的版本历史就像这样:

rebase 合并图

可以看见 master 分支从 deve 分支最新提交 35b6708 开始依次提交了自己的三个 commit(由于是提取修改后重新依次提交,故 commit 的 hash 码与上面的85841be、a016f64、e53ec51 不同)

git rebase 目标分支
其实是先将HEAD指向目标分支和当前分支的共同祖先commit节点,然后将当前分支上的commit一个一个的apply到目标分支上,apply完以后再将HEAD指向当前分支。

注意,拆掉的是当前分支,往rebse指令后的目标分支上合,上面例子中拆掉了master的提交记录,慎重!如果在开发中你需要force push,说明你做反了,把服务器代码rebase到你本地分支之上才会需要force push,这是错误的用法。

(3)冲突处理策略的不同
merge 遇见冲突后会直接停止,等待手动解决冲突并重新提交 commit 后,才能再次 merge
rebase 遇见冲突后会暂停当前操作,开发者可以选择手动解决冲突,然后 git rebase --continue 继续,或者 --skip 跳过(注意此操作中当前分支的修改会直接覆盖目标分支的冲突部分),亦或者 --abort 直接停止该次 rebase 操作

(4)merge --no-ff与 merge --ff-only的区别
上面对 merge 的讲述都是基于其默认操作即 --no-ff(git merge xxx = git merge --no-ff xxx)的说明,但是 merge 还有一种常用的选项 --ff-only,那么这两种有什么区别呢?
--no-ff 是 merge 的默认操作,三方合并并提交修改;而 --ff-only 会判断当前分支可否根据目标分支快速合并,就像下面这样

快速合并

此时 deve 分支就可与 master 分支快速合并。
在 deve 分支上 git merge --ff-only master,便得到合并完成后的版本历史图

快速合并完成

可以发现 --ff-only生成的历史记录和 rebase 十分相似,但是本质上 --ff-only仍然是合并操作,但 rebase 并没有做合并,仅仅是提取修改到目标分支后面。

(5)总结:选择 merge 还是 rebase?

  • merge 是一个合并操作,会将两个分支的修改合并在一起,默认操作的情况下会提交合并中修改的内容
  • merge 的提交历史忠实地记录了实际发生过什么,关注点在真实的提交历史上面
  • rebase 并没有进行合并操作,只是提取了当前分支的修改,将其复制在了目标分支的最新提交后面
  • rebase 的提交历史反映了项目过程中发生了什么,关注点在开发过程上面
  • merge 与 rebase 都是非常强大的分支整合命令,没有优劣之分,使用哪一个应由项目和团队的开发需求决定

(6)最后:一些注意点

  • 使用 merge 时应考虑是采用 --no-ff 默认操作,生成一个对回顾提交历史并不友好的合并记录,还是采用 --ff-only 方式
  • rebase 操作会丢弃当前分支已提交的 commit,故不要在已经 push 到远程,和其他人正在协作开发的分支上执行 rebase 操作
  • 与远程仓库同步时,使用 pull 命令默认进行了 git fetch + git merge --no-ff 两个操作,可以通过加上 --rebase 命令将 fetch 后的 merge 操作改为 rebase 操作,或者仅仅 'git fetch remoteName',然后才思考采取哪种整合策略 git merge(or rebase) origin/master
  • 开发与 commit 时注意自己此时在哪个分支上
  • 当有修改未 commit 时,不能进行 rebase 操作,此时可以考虑先用 git stash 命令暂存

(7)fast-forward
参考Fast-Forward Git合并
当前分支合并到另一分支时,如果没有分歧解决,就会直接移动文件指针。这个过程叫做fastforward。
举例来说,开发一直在master分支进行,但忽然有一个新的想法,于是新建了一个develop的分支,并在其上进行一系列提交,完成时,回到 master分支,此时,master分支在创建develop分支之后并未产生任何新的commit。此时的合并就叫fast forward。
使用—no-ff (no fast foward),使得每一次的合并都创建一个新的commit记录。即使这个commit只是fast-foward,用来避免丢失信息。
可以看出,使用no-ff后,会多生成一个commit 记录,并强制保留develop分支的开发记录(而fast-forward的话则是直接合并,看不出之前Branch的任何记录)。这对于以后代码进行分析特别有用。

(8)rebase的风险
参考
洁癖者用 Git:pull --rebase 和 merge --no-ff
Git 分支 - 分支的衍合
一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用 git rebase抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。
下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发,提交历史类似图 3-36 所示:

图 3-36. 克隆一个仓库,在其基础上工作一番

现在,某人(注:注意这个坏人做了什么)在 C1 的基础上做了些改变,并合并他自己的分支得到结果 C6,推送到中央服务器。当你抓取并合并这些数据到你本地的开发分支中后,会得到合并结果 C7,历史提交会变成图 3-37 这样:

图 3-37. 抓取他人提交,并入自己主干

接下来,那个推送 C6 上来的人决定用衍合取代之前的合并操作;继而又用 git push --force覆盖了服务器上的历史,得到 C4'(注:就是这里出的问题)。而之后当你再从服务器上下载最新提交后,会得到:

有人推送了衍合后得到的 C4',丢弃了你作为开发基础的 C4 和 C6

下载更新后需要合并,但此时衍合产生的提交对象 C4' 的 SHA-1 校验值和之前 C4 完全不同,所以 Git 会把它们当作新的提交对象处理,而实际上此刻你的提交历史 C7 中早已经包含了 C4 的修改内容,于是合并操作会把 C7 和 C4' 合并为 C8(见图 3-39):

图 3-39. 你把相同的内容又合并了一遍,生成一个新的提交 C8

C8 这一步的合并是迟早会发生的,因为只有这样你才能和其他协作者提交的内容保持同步。而在 C8 之后,你的提交历史里就会同时包含 C4 和 C4',两者有着不同的 SHA-1 校验值,如果用 git log查看历史,会看到两个提交拥有相同的作者日期与说明,令人费解。而更糟的是,当你把这样的历史推送到服务器后,会再次把这些衍合后的提交引入到中央服务器,进一步困扰其他人(译注:这个例子中,出问题的责任方是那个发布了 C6 后又用衍合发布 C4' 的人,其他人会因此反馈双重历史到共享主干,从而混淆大家的视听。)。
如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些尚未公开的提交对象,就没问题。如果衍合那些已经公开的提交对象,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦。

上面这个坏人使用了git push --force来强制推送rebase,以下参考团队开发里频繁使用 git rebase 来保持树的整洁好吗?

git rebase是对commit history的改写。当你要改写的commit history还没有被提交到远程repo的时候,也就是说,还没有与他人共享之前,commit history是你私人所有的,那么想怎么改写都可以。
而一旦被提交到远程后,这时如果再改写history,那么势必和他人的history长的就不一样了。git push的时候,git会比较commit history,如果不一致,commit动作会被拒绝,唯一的办法就是带上-f参数,强制要求commit,这时git会以committer的history覆写远程repo,从而完成代码的提交。虽然代码提交上去了,但是这样可能会造成别人工作成果的丢失,所以使用-f参数要慎重。

(9)分支推送到远程,或者删除远程分支
参考Git 分支 - 远程分支
推送本地分支
要想和其他人分享某个本地分支,你需要把它推送到一个你拥有写权限的远程仓库。你创建的本地分支不会因为你的写入操作而被自动同步到你引入的远程服务器上,你需要明确地执行推送分支的操作。换句话说,对于无意分享的分支,你尽管保留为私人分支好了,而只推送那些协同工作要用到的特性分支。

如果你有个叫 serverfix 的分支需要和他人一起开发,可以运行 git push origin serverfix,也可以运行 git push origin serverfix:serverfix来实现相同的效果,它的意思是“上传我本地的 serverfix 分支到远程仓库中去,仍旧称它为 serverfix 分支”。通过此语法,你可以把本地分支推送到某个命名不同的远程分支:若想把远程分支叫作 awesomebranch,可以用git push origin serverfix:awesomebranch 来推送数据。

删除远程分支
如果不再需要某个远程分支了,比如搞定了某个特性并把它合并进了远程的 master 分支(或任何其他存放稳定代码的分支),可以用这个非常无厘头的语法来删除它:git push origin :serverfix。有种方便记忆这条命令的方法:记得前面的 git push [远程名] [本地分支]:[远程

版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/9eaf603b4796
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-09 21:25:34
  • 阅读 ( 1990 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢