10
10
- [ 通过` git log ` 查看版本演变历史] ( #通过git-log查看版本演变历史 )
11
11
- [ 探密` .git ` 目录] ( #探密`.git`目录 )
12
12
- [ 理解` git reset ` ] ( #理解git-reset )
13
+ - [ 如何撤销 Git 操作?] ( #如何撤销git操作 )
14
+ - [ git cherry-pick] ( #git-cherry-pick )
13
15
- [ 怎么修改老旧` commit ` 的` message ` ] ( #怎么修改老旧commit的message )
14
16
- [ 怎样把连续的多个` commit ` 整理成1个] ( #怎样把连续的多个commit整理成1个 )
15
17
- [ 添加忽略配置文件` .gitignore ` ] ( #添加忽略配置文件gitignore )
@@ -246,24 +248,24 @@ git commit --amend 对最近一次的commit信息进行修改
246
248
247
249
下面这一段是另外一个牛人的解释:
248
250
总的来说,git reset命令是用来将当前branch重置到另外一个commit的,而这个动作可能会将index以及work tree同样影响。比如如果你的master branch(当前checked out)是下面这个样子:
249
- ```
251
+ ``` sh
250
252
- C (HEAD, master)
251
253
- B
252
254
- A
253
255
```
254
256
HEAD和master branch tip是在一起的,而你希望将master指向到B,而不是C,那么你执行` git reset B ` 以便移动master branch到B那个commit:
255
- ```
257
+ ``` sh
256
258
- B (HEAD, master) # - C is still here, but there's no branch pointing to it anymore
257
259
- A
258
260
```
259
261
注意:git reset和checkout是不一样的。如果你运行git checkout B,那么你讲得到:
260
- ```
262
+ ``` sh
261
263
- C (master)
262
264
- B (HEAD)
263
265
- A
264
266
```
265
267
这时HEAD和master branch就不在一个点上了,你进入detached HEAD STATE. HEAD,work tree,index都指向了B,但是master branch却依然指向C。如果在这个点上,你执行一个新的commit D,那么你讲得到下面(当然这可能并不是你想要的,你可能想要的是创一个branch做bug fix):
266
- ```
268
+ ``` sh
267
269
- A - B - A (master)
268
270
\
269
271
D (HEAD)
@@ -274,47 +276,235 @@ HEAD和master branch tip是在一起的,而你希望将master指向到B,而
274
276
如果你仔细研究` reset ` 命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit。假设我们有一个分支(名称本身无所谓,所以我们就简单称为"super-duper-feature”分支吧),图形化表示如下:
275
277
![ git reset] ( ./images/git-reset1.png )
276
278
如果我们执行:
277
- ``` git
279
+ ``` sh
278
280
git reset HEAD
279
281
```
280
282
任何事情都不会发生,这是因为我们告诉GIT重置这个分支到` HEAD ` ,而这个正是它现在所在的位置。
281
283
282
- ``` git
284
+ ``` sh
283
285
git reset HEAD~1
284
286
```
285
287
当我们再执行上面的命令时(` HEAD~1 ` 是` the commit right before HEAD ` 的别名,或者说:put differently "HEAD's parent"),我们的分支将会如下所示:
286
288
![ git reset] ( ./images/git-reset2.png )
287
289
如果我们执行` git reset HEAD~2 ` ,则意味着将` HEAD ` 从顶端的commit往下移动两个更早的commit。
288
290
289
291
### git reset --soft
290
- ``` git
292
+ ``` sh
291
293
git reset --soft commit-ID
292
294
```
293
295
` --soft ` 参数告诉Git重置` HEAD ` 到另外一个commit,但也到此为止。如果你指定` --soft ` 参数,Git将停止在那里而什么也不会根本变化。这意味着` index ` , ` working copy ` 都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在` stage(index) ` 区域中。
294
296
![ git reset] ( ./images/git-reset3.png )
295
297
296
298
### git reset --hard
297
- ``` git
299
+ ``` sh
298
300
git reset --hard commit-ID
299
301
```
300
302
` --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!
301
303
![ git reset] ( ./images/git-reset4.png )
302
304
303
305
### git reset
304
- ``` git
306
+ ``` sh
305
307
git reset commit-ID
306
308
# or
307
309
git reset --mixed commit-ID
308
310
```
309
311
` --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.
310
312
![ git reset] ( ./images/git-reset5.png )
311
313
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
+
312
326
### 总结
313
327
![ git reset] ( ./images/git-reset.jpg )
314
328
- ** --soft** : uncommit changes, changes are left staged (index).
315
329
- ** --mixed (default)** : uncommit + unstage changes, changes are left in working tree.
316
330
- ** --hard** : uncommit + unstage + delete changes, nothing left.
317
331
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
+ ```
318
508
319
509
## 怎么修改老旧commit的message
320
510
``` bash
@@ -859,4 +1049,6 @@ git 最好 学习 资料 in:readme stars:>1000 language:c
859
1049
> 3. [GitHub官网给出的例子](https://github.com/github/gitignore)
860
1050
> 4. [Git submodule 子模块的管理和使用](https://www.jianshu.com/p/9000cd49822c)
861
1051
> 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)
0 commit comments