Skip to content

Commit 734f1ef

Browse files
committed
add git cherry-pick
1 parent 2e55c5d commit 734f1ef

File tree

2 files changed

+202
-10
lines changed

2 files changed

+202
-10
lines changed

README.md

Lines changed: 202 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- [通过`git log`查看版本演变历史](#通过git-log查看版本演变历史)
1111
- [探密`.git`目录](#探密`.git`目录)
1212
- [理解`git reset`](#理解git-reset)
13+
- [如何撤销 Git 操作?](#如何撤销git操作)
14+
- [git cherry-pick](#git-cherry-pick)
1315
- [怎么修改老旧`commit``message`](#怎么修改老旧commit的message)
1416
- [怎样把连续的多个`commit`整理成1个](#怎样把连续的多个commit整理成1个)
1517
- [添加忽略配置文件`.gitignore`](#添加忽略配置文件gitignore)
@@ -246,24 +248,24 @@ git commit --amend 对最近一次的commit信息进行修改
246248

247249
下面这一段是另外一个牛人的解释:
248250
总的来说,git reset命令是用来将当前branch重置到另外一个commit的,而这个动作可能会将index以及work tree同样影响。比如如果你的master branch(当前checked out)是下面这个样子:
249-
```
251+
```sh
250252
- C (HEAD, master)
251253
- B
252254
- A
253255
```
254256
HEAD和master branch tip是在一起的,而你希望将master指向到B,而不是C,那么你执行`git reset B` 以便移动master branch到B那个commit:
255-
```
257+
```sh
256258
- B (HEAD, master) # - C is still here, but there's no branch pointing to it anymore
257259
- A
258260
```
259261
注意:git reset和checkout是不一样的。如果你运行git checkout B,那么你讲得到:
260-
```
262+
```sh
261263
- C (master)
262264
- B (HEAD)
263265
- A
264266
```
265267
这时HEAD和master branch就不在一个点上了,你进入detached HEAD STATE. HEAD,work tree,index都指向了B,但是master branch却依然指向C。如果在这个点上,你执行一个新的commit D,那么你讲得到下面(当然这可能并不是你想要的,你可能想要的是创一个branch做bug fix):
266-
```
268+
```sh
267269
- A - B - A (master)
268270
\
269271
D (HEAD)
@@ -274,47 +276,235 @@ HEAD和master branch tip是在一起的,而你希望将master指向到B,而
274276
如果你仔细研究`reset`命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit。假设我们有一个分支(名称本身无所谓,所以我们就简单称为"super-duper-feature”分支吧),图形化表示如下:
275277
![git reset](./images/git-reset1.png)
276278
如果我们执行:
277-
```git
279+
```sh
278280
git reset HEAD
279281
```
280282
任何事情都不会发生,这是因为我们告诉GIT重置这个分支到`HEAD`,而这个正是它现在所在的位置。
281283

282-
```git
284+
```sh
283285
git reset HEAD~1
284286
```
285287
当我们再执行上面的命令时(`HEAD~1``the commit right before HEAD`的别名,或者说:put differently "HEAD's parent"),我们的分支将会如下所示:
286288
![git reset](./images/git-reset2.png)
287289
如果我们执行`git reset HEAD~2`,则意味着将`HEAD`从顶端的commit往下移动两个更早的commit。
288290

289291
### git reset --soft
290-
```git
292+
```sh
291293
git reset --soft commit-ID
292294
```
293295
`--soft`参数告诉Git重置`HEAD`到另外一个commit,但也到此为止。如果你指定`--soft`参数,Git将停止在那里而什么也不会根本变化。这意味着`index`, `working copy`都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在`stage(index)`区域中。
294296
![git reset](./images/git-reset3.png)
295297

296298
### git reset --hard
297-
```git
299+
```sh
298300
git reset --hard commit-ID
299301
```
300302
`--hard`参数将会blow out everything.它将重置`HEAD`返回到另外一个commit(取决于~12的参数),重置`index`以便反映`HEAD`的变化,并且重置`working copy`也使得其完全匹配起来。这是一个比较危险的动作,具有破坏性,数据因此可能会丢失!如果真是发生了数据丢失又希望找回来,那么只有使用:git reflog命令了。makes everything match the commit you have reset to.你的所有本地修改将丢失。如果我们希望彻底丢掉本地修改但是又不希望更改branch所指向的commit,则执行`git reset --hard` = `git reset --hard HEAD`. i.e. don't change the branch but get rid of all local changes.另外一个场景是简单地移动branch从一个到另一个commit而保持index/work区域同步。这将确实令你丢失你的工作,因为它将修改你的work tree!
301303
![git reset](./images/git-reset4.png)
302304

303305
### git reset
304-
```git
306+
```sh
305307
git reset commit-ID
306308
# or
307309
git reset --mixed commit-ID
308310
```
309311
`--mixed``reset`的默认参数,也就是当你不指定任何参数时的参数。它将重置`HEAD`到另外一个commit,并且重置`index`以便和`HEAD`相匹配,但是也到此为止。`working copy`不会被更改。所有该branch上从original HEAD(commit)到你重置到的那个commit之间的所有变更将作为local modifications保存在working area中,(被标示为local modification or untracked via git status),但是并未staged的状态,你可以重新检视然后再做修改和commit.
310312
![git reset](./images/git-reset5.png)
311313

314+
### 撤销git reset --hard
315+
```sh
316+
# step 1 查看git reset --hard之前的删除的log
317+
git reflog
318+
319+
# step 2 恢复
320+
git reset –hard commit-ID
321+
322+
# or
323+
git cherry-pick commit-ID
324+
```
325+
312326
### 总结
313327
![git reset](./images/git-reset.jpg)
314328
- **--soft**: uncommit changes, changes are left staged (index).
315329
- **--mixed (default)**: uncommit + unstage changes, changes are left in working tree.
316330
- **--hard**: uncommit + unstage + delete changes, nothing left.
317331

332+
## 如何撤销git操作
333+
### 撤销提交
334+
一种常见的场景是,提交代码以后,你突然意识到这个提交有问题,应该撤销掉,这时执行下面的命令就可以了。
335+
```sh
336+
git revert HEAD
337+
```
338+
上面命令的原理是,在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化。它不会改变过去的历史,所以是首选方式,没有任何丢失代码的风险。
339+
340+
git revert 命令只能抵消上一个提交,如果想抵消多个提交,必须在命令行依次指定这些提交。比如,抵消前两个提交,要像下面这样写。
341+
```sh
342+
git revert [倒数第一个提交] [倒数第二个提交]
343+
```
344+
`git revert`命令还有两个参数。
345+
- **--no-edit**:执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息。
346+
- **--no-commit**:只抵消暂存区和工作区的文件变化,不产生新的提交。
347+
348+
### 丢弃提交
349+
如果希望以前的提交在历史中彻底消失,而不是被抵消掉,可以使用`git reset`命令,丢弃掉某个提交之后的所有提交。
350+
```sh
351+
git reset [last good SHA]
352+
```
353+
`git reset`的原理是,让最新提交的指针回到以前某个时点,该时点之后的提交都从历史中消失。
354+
355+
默认情况下,`git reset`不改变工作区的文件(但会改变暂存区),--hard参数可以让工作区里面的文件也回到以前的状态。
356+
```sh
357+
git reset --hard [last good SHA]
358+
```
359+
执行`git reset`命令之后,如果想找回那些丢弃掉的提交,可以使用`git reflog`命令,具体做法参考这里。不过,这种做法有时效性,时间长了可能找不回来。
360+
361+
### 替换上一次提交
362+
提交以后,发现提交信息写错了,这时可以使用`git commit`命令的`--amend`参数,可以修改上一次的提交信息。
363+
```sh
364+
git commit --amend -m "Fixes bug #42"
365+
```
366+
它的原理是产生一个新的提交对象,替换掉上一次提交产生的提交对象。
367+
368+
这时如果暂存区有发生变化的文件,会一起提交到仓库。所以,`--amend`不仅可以修改提交信息,还可以整个把上一次提交替换掉。
369+
370+
### 撤销工作区的文件修改
371+
如果工作区的某个文件被改乱了,但还没有提交,可以用`git checkout`命令找回本次修改之前的文件。
372+
```sh
373+
git checkout -- [filename]
374+
```
375+
它的原理是先找暂存区,如果该文件有暂存的版本,则恢复该版本,否则恢复上一次提交的版本。
376+
377+
注意,工作区的文件变化一旦被撤销,就无法找回了。
378+
379+
### 从暂存区撤销文件
380+
如果不小心把一个文件添加到暂存区,可以用下面的命令撤销。
381+
```sh
382+
git rm --cached [filename]
383+
```
384+
上面的命令不影响已经提交的内容。
385+
386+
### 撤销当前分支的变化
387+
你在当前分支上做了几次提交,突然发现放错了分支,这几个提交本应该放到另一个分支。
388+
```sh
389+
# 新建一个 feature 分支,指向当前最新的提交
390+
# 注意,这时依然停留在当前分支
391+
git branch feature
392+
393+
# 切换到这几次提交之前的状态
394+
git reset --hard [当前分支此前的最后一次提交]
395+
396+
# 切换到 feature 分支
397+
git checkout feature
398+
```
399+
上面的操作等于是撤销当前分支的变化,将这些变化放到一个新建的分支。
400+
401+
## git cherry-pick
402+
对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。
403+
404+
这时分两种情况。一种情况是,你需要另一个分支的所有代码变动,那么就采用合并(git merge)。另一种情况是,你只需要部分代码变动(某几个提交),这时可以采用 Cherry pick。
405+
406+
![git cherry-pick](./images/git-cherry-pick.jpg)
407+
408+
### 基本用法
409+
`git cherry-pick`命令的作用,就是将指定的提交(commit)应用于其他分支。
410+
```sh
411+
git cherry-pick <commitHash>
412+
```
413+
上面命令就会将指定的提交commitHash,应用于当前分支。这会在当前分支产生一个新的提交,当然它们的哈希值会不一样。
414+
415+
举例来说,代码仓库有master和feature两个分支。
416+
```sh
417+
a - b - c - d Master
418+
\
419+
e - f - g Feature
420+
```
421+
现在将提交f应用到master分支。
422+
```sh
423+
# 切换到 master 分支
424+
$ git checkout master
425+
426+
# Cherry pick 操作
427+
$ git cherry-pick f
428+
```
429+
上面的操作完成以后,代码库就变成了下面的样子。
430+
```sh
431+
a - b - c - d - f Master
432+
\
433+
e - f - g Feature
434+
```
435+
从上面可以看到,master分支的末尾增加了一个提交f。
436+
437+
`git cherry-pick`命令的参数,不一定是提交的哈希值,分支名也是可以的,表示转移该分支的最新提交。
438+
```sh
439+
git cherry-pick feature
440+
```
441+
上面代码表示将feature分支的最近一次提交,转移到当前分支。
442+
443+
### 转移多个提交
444+
Cherry pick 支持一次转移多个提交。
445+
```sh
446+
git cherry-pick <HashA> <HashB>
447+
```
448+
上面的命令将 **A****B** 两个提交应用到当前分支。这会在当前分支生成两个对应的新提交。
449+
450+
如果想要转移一系列的连续提交,可以使用下面的简便语法。
451+
```sh
452+
git cherry-pick A..B
453+
```
454+
上面的命令可以转移从 A 到 B 的所有提交。它们必须按照正确的顺序放置:提交 A 必须早于提交 B,否则命令将失败,但不会报错。
455+
456+
注意,使用上面的命令,提交 A 将不会包含在 Cherry pick 中。如果要包含提交 A,可以使用下面的语法。
457+
```sh
458+
git cherry-pick A^..B
459+
```
460+
### 配置项
461+
`git cherry-pick`命令的常用配置项如下:
462+
- **-e****--edit** : 打开外部编辑器,编辑提交信息。
463+
- **-n****--no-commit** : 只更新工作区和暂存区,不产生新的提交。
464+
- **-x** : 在提交信息的末尾追加一行(cherry picked from commit ...),方便以后查到这个提交是如何产生的。
465+
- **-s****--signoff** : 在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。
466+
- **-m parent-number****--mainline parent-number** : 如果原始提交是一个合并节点,来自于两个分支的合并,那么 Cherry pick 默认将失败,因为它不知道应该采用哪个分支的代码变动。
467+
468+
-m配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number是一个从1开始的整数,代表原始提交的父分支编号。
469+
```sh
470+
git cherry-pick -m 1 <commitHash>
471+
```
472+
上面命令表示,Cherry pick 采用提交commitHash来自编号1的父分支的变动。
473+
474+
一般来说,1号父分支是接受变动的分支(the branch being merged into),2号父分支是作为变动来源的分支(the branch being merged from)。
475+
476+
### 代码冲突
477+
478+
如果操作过程中发生代码冲突,Cherry pick 会停下来,让用户决定如何继续操作。
479+
480+
- **--continue** : 用户解决代码冲突后,第一步将修改的文件重新加入暂存区(git add .),第二步使用下面的命令,让 Cherry pick 过程继续执行。
481+
```sh
482+
git cherry-pick --continue
483+
```
484+
- **--abort** : 发生代码冲突后,放弃合并,回到操作前的样子。
485+
- **--quit** : 发生代码冲突后,退出 Cherry pick,但是不回到操作前的样子。
486+
487+
### 转移到另一个代码库
488+
`Cherry pick` 也支持转移另一个代码库的提交,方法是先将该库加为远程仓库。
489+
```sh
490+
git remote add target git://gitUrl
491+
```
492+
上面命令添加了一个远程仓库target。
493+
494+
然后,将远程代码抓取到本地。
495+
```sh
496+
git fetch target
497+
```
498+
上面命令将远程代码仓库抓取到本地。
499+
500+
接着,检查一下要从远程仓库转移的提交,获取它的哈希值。
501+
```sh
502+
git log target/master
503+
``
504+
最后,使用`git cherry-pick`命令转移提交。
505+
```sh
506+
git cherry-pick <commitHash>
507+
```
318508

319509
## 怎么修改老旧commit的message
320510
```bash
@@ -859,4 +1049,6 @@ git 最好 学习 资料 in:readme stars:>1000 language:c
8591049
> 3. [GitHub官网给出的例子](https://github.com/github/gitignore)
8601050
> 4. [Git submodule 子模块的管理和使用](https://www.jianshu.com/p/9000cd49822c)
8611051
> 5. [git reset soft,hard,mixed之区别深解](https://www.cnblogs.com/kidsitcn/p/4513297.html)
862-
> 6. [What's the difference between git reset --mixed, --soft, and --hard?](https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard)
1052+
> 6. [What's the difference between git reset --mixed, --soft, and --hard?](https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard)
1053+
> 7. [如何撤销 Git 操作?](http://www.ruanyifeng.com/blog/2019/12/git-undo.html)
1054+
> 8. [git cherry-pick 教程](https://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html)

images/git-cherry-pick.jpg

11.6 KB
Loading

0 commit comments

Comments
 (0)