likes
comments
collection
share

Git命令中的restore是什么?

作者站长头像
站长
· 阅读数 10

git-restore的作用,简单来说就是用于恢复缓存区(别名叫做索引区或index)和工作区(working directory)的资源文件,使他们回滚到上一次提交到(add操作)缓存区或上一次commit的状态。在使用命令时可以指定不同的可选项,使恢复内容和位置更加灵活多变。

使用概要

下面是使用概要,可以在看完使用方法后再回来看会更加清晰,可以先跳过:

git restore [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]

可选参数

使用简写时注意大小写。

--worktree(-W)和--staged(-S)

通过使用--worktree和--staged选项可以指定恢复的位置是工作区(working tree)或缓存区。如果都不指定,那么默认还原的是当前的工作区,一般是HEAD指向的commit。

--staged

--staged可以让还原位置指向缓存区。

举个例子,假设项目里有两个文件hello.txt和world.txt,将两个文件的内容分别做如下修改:

hello.txt文件

hello.txt文件内容

world.txt文件

world.txt文件内容

然后使用git add hello.txt命令将hello.txt文件添加到缓存区中,而world.txt文件保持在工作区中不变。可以使用git status命令查看文件状态:

Git命令中的restore是什么?

如上图所示hello.txt已经是modified,说明已经添加到缓存区中,而world.txt是unstaged,说明仍然保持在工作区中。

如果此时想撤销已经提交到缓存区的文件改动,则可以使用git restore --staged <文件路径> 来进行文件逆向操作,将文件从缓存区中移除也就是变成unstaged状态。尝试输入:

git restore --staged . // 回滚文件状态
git status // 查看文件状态

执行命令后:

Git命令中的restore是什么?

注意看此时hello.txt文件的状态变为了unstaged,和world.txt文件一样,说明文件已经被成功还原了。

--worktree

--worktree可以让还原的位置指向工作区。

还是以上面这个例子举例,假设项目里有两个文件hello.txt和world.txt,修改如下:

hello.txt文件

hello.txt文件内容

world.txt文件

world.txt文件内容

然后使用git add命令将hello.txt添加到缓存区中,工作区只剩下world.txt文件是unstaged。如果此时想还原工作区,则可以使用--worktree让restore只还原工作区的内容,操作如下:

git restore .
// 等同于
git restore --worktree .
// 等同于
git restore -W .

执行命令之后,使用git status查看:

Git命令中的restore是什么?

结果如上,可以看到此时world.txt文件从工作区中被还原成了未做修改时的状态,而hello.txt仍然在缓存区中保持不变。

除了使用restore恢复文件内容外,还可以使用它恢复被误删除的文件。举个🌰,将上述例子中的hello.txt文件删除,然后使用git status命令查看内容:

Git命令中的restore是什么?

此时的hello.txt文件状态是deleted,如果想恢复文件则可以使用git restore命令:

git restore hello.txt
// 等同于
git restore --worktree hello.txt
// 等同于
git restore -W hello.txt

执行命令后,使用git status查看:

Git命令中的restore是什么?

会提示工作区的内容,没有任何的改动,hello.txt文件也成功被恢复了。

注意:--worktree和--staged是可以一起使用的,它们会把缓存区和工作区一起进行恢复。

--source=<tree>(-s <tree>)

该选参的作用是将要恢复的工作区从指定的工作树(working tree)中恢复。这个参数--source的值可以是某个commit、branch或者tag。注意:如果--source没有被指定,同时--staged被指定了,文件会从HEAD指向的commit中进行恢复,否则一旦--source被指定,会默认从缓存区(index)进行恢复。

举个例子:

假设某个分支中有两个commit。第一个commit增加了a.js,第二个commit中增加了b.js然后删除掉了a.js。

Git命令中的restore是什么?

此时项目中只有b.js,但是在一段时间开发后发现现在又需要a.js文件了,咋办?难道需要再切回上一个commit中拷贝a.js文件吗?no! no! no! 这么愚蠢的办法,身为高级摸鱼工程师怎么能把时间浪费在这上面,接下来使用restore来快速恢复文件:

// 这里使用第一个commit的hash
git restore --source=63e02b3c5bb9605f1e6c8dd6bc51b466a2699d74 a.js
// 相当于
git restore --source=63e02b3c5bb9605f1e6c8dd6bc51b466a2699d74 --worktree a.js

在执行完命令后,我们发现a.js文件被成功的恢复到了项目中,并且状态是Untracked file。因为--source被指定了所以应该从缓存区中读取a.js,并且--staged和--worktree都未被指定,所以会写入到当前的工作区上,相当新创建一个a.js文件,所以文件状态是Untracked file。

Git命令中的restore是什么?

接着使用--staged选参试试看:

// 这里使用第一个commit的hash
git restore --source=63e02b3c5bb9605f1e6c8dd6bc51b466a2699d74 --staged a.js

奇怪的是,在项目中并没有发现有a.js,难道是恢复失败了吗?

Git命令中的restore是什么?

使用git status a.js查看文件的状态,我们发现a.js文件先是被添加然后又被删除了。猜测是因为只在缓存区中恢复了文件,并没有在工作区中恢复文件才会导致文件状态是被删除。

Git命令中的restore是什么?

所以应该恢复文件同时加上--staged和--worktree在缓存区和工作区中同时恢复文件:

// 这里使用第一个commit的hash
git restore --source=63e02b3c5bb9605f1e6c8dd6bc51b466a2699d74 --staged --worktree a.js

此时的a.js文件的状态就是已经被写入缓存区和工作区中。

Git命令中的restore是什么?

所以在使用--source时,最好同时指定--staged和--worktree。

--patch(-p)

使用该参数后会以一种交互式的方式来询问如何处理文件每一处的修改(hunk)。

假设项目根目录中有一个a.js文件,我们来随便写点东西:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(this.name + ':say hi');
  }
  sayGoodBye() {
    console.log(this.name + ':say goodbye');
  }
}

const somebody = new Person('tom');

根目录还有一个b.js文件:

console.log('b.js');

将这两个文件commit到本地仓库中。

随后我们对工作区的文件做两处改动,首先将a.js进行内容修改:

class Person {
  constructor(name, age) { // 新增age参数
    this.name = name;
    this.age = age; // 新增代码
  }
  sayHi() {
    console.log(this.name + ':say hi');
  }
  sayGoodBye() {
    console.log(this.name + ':say goodbye');
  }
}

const somebody = new Person('tom', 28); // 新增参数

然后直接将b.js文件删除。

这时候,如果只想恢复文件中的部分代码,而不是一整个文件,怎么办?是的,那就是使用--patch选项配合restore:

git restore --patch

Git命令中的restore是什么?

我们看到控制台里面出现了可以交互的界面,并且询问我们是否要放弃本次修改在当前工作树中。并且本次文件修改被分为了两个hunk,也就是分为了两段代码来让用户确认修改。这个命令和git diff所展示内容似乎是一模一样的。

git中的每一次修改都会被认为是一个hunk,根据特定的方式来进行分割。

我们来试试使用git diff:

Git命令中的restore是什么?

git diff如果不加任何参数会将所有文件的变动一次性全部输出,而使用git restore --patch会将有变动的文件一个一个询问来让我们处理是否要回滚,也就是第一张图蓝字的部分。

我们看到处理文件的选项有[y,n,q,a,d,j,J,g,/,s,e,?],这几个英文字母又代表什么意思呢?这时候就可以使用?符号在控制台打印出帮助说明,我们先来看看对应的英文说明:

y - discard this hunk from worktree

n - do not discard this hunk from worktree

q - quit; do not discard this hunk or any of the remaining ones

a - discard this hunk and all later hunks in the file

d - do not discard this hunk or any of the later hunks in the file

g - select a hunk to go to

/ - search for a hunk matching the given regex

j - leave this hunk undecided, see next undecided hunk

J - leave this hunk undecided, see next hunk

s - split the current hunk into smaller hunks

e - manually edit the current hunk

? - print help

这里一个一个选项来试并查看结果:

1.(y)

输入y将会抛弃本次hunk在工作树中的修改,接着会询问第二个hunk的处理方式:

Git命令中的restore是什么?

2.(n)

n的作用就是不抛弃本次修改。

我们选择n并回车,修改的代码将会被保留下来,操作完毕后对a.js文件改动就结束了:

const Person {
  constructor(name) { // age参数被删除了
    this.name = name;
    // this.age = age 被删除了
  }
  sayHi() {
    console.log(this.name + ':say hi');
  }
  sayGoodBye() {
    console.log(this.name + ':say goodbye');
  }
}

const somebody = new Person('tom', 28); // 传入28的参数被保留了

3.(q)

如果想退出本次restore,不对本次的hunks做任何的restore,这时候可以直接输入q。

再次回到对a.js做第一次修改的时候:

class Person {
  constructor(name, age) { // 新增代码
    this.name = name;
    this.age = age; // 新增代码
  }
  sayHi() {
    console.log(this.name + ':say hi');
  }
  sayGoodBye() {
    console.log(this.name + ':say goodbye');
  }
}

const somebody = new Person('tom', 28); // 新增代码

然后使用git restore --patch命令,接着输入q放弃本次的所有restore操作(包括放弃后面删除b.js的restore):

Git命令中的restore是什么?

4.(a)

使用a就相当于直接使用git restore不带任何参数,抛弃本次单个文件内所有的hunk做的代码修改。

Git命令中的restore是什么?

一旦我们输入a并回车后,文件就会变成上一次commit的状态:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(this.name + ':say hi');
  }
  sayGoodBye() {
    console.log(this.name + ':say goodbye');
  }
}

const somebody = new Person('tom');

这样就处理完了第一个a.js文件,接着会继续询问是否对b.js进行restore,根据自己的需要选择对应的参数就可以。

5.(d)

使用d将会接受对本文件的所有hunk修改,然后询问对下一个文件的restore:

Git命令中的restore是什么?

在输入d参数后,a.js文件保留了对该文件的所有hunk,然后继续询问我们对b.js的hunk。

6.(g)

使用g命令就能不按顺序,指定想要更改哪个hunk:

Git命令中的restore是什么?

在输入g后,出现了2个选项并且询问要去哪个hunk,输入2并回车,直接跳转到了第二个hunk中:

Git命令中的restore是什么?

7.(/)

使用/将会以正则表达式匹配hunk修改的内容,比如如果想直接跳转到第二个hunk里面,我们可以直接输入28来匹配:

Git命令中的restore是什么?

我们看到此时已经跳转到了第二个hunk,并询问我们处理的方式。

8.(J)

使用J可以暂时跳过当前hunk,去处理下一个hunk。

Git命令中的restore是什么?

如上图,在处理第一个hunk时,我们输入了J来暂时跳过这个hunk的restore转而进入第二个hunk的处理。

9.(j)

j的作用和J很相似,只不过j是跳转到下一个之前跳过的未处理的hunk。在上面J的例子中,使用了J跳过了第一个hunk,此时再使用K跳过第二个hunk,返回上一个hunk,也可以使用k跳过第二个hunk,返回上一个未处理的hunk。接着可以再输入j查看结果:

Git命令中的restore是什么?

如上图,成功跳转到了下一个未处理的hunk。

10.(s)

使用s可以将hunk拆分成更细粒度的多个hunks。

Git命令中的restore是什么?

如上图,将第一个hunk拆分成更细粒度的两个hunks。

11.(e)

使用e选项可以手动的修改当前的hunk:

Git命令中的restore是什么?

如上图,可以用类似vim编辑器去手动的修改hunk。

--ours、--theirs

当文件存在冲突(使用merge或者rebase等操作会产生冲突)处于unmerged paths,可以使用--ours或者--theirs来将文件restore成其中的一个版本。

假如有一个项目,这个项目有一个master分支和feature分支,在master分支中有一个名称为a的commit记录,这个commit中添加了一个文件weeks.txt并写入内容Monday。在feature分支中,也有一个weeks.txt文件并且写入内容为Tuesday。接着将feature分支合并到master上,此时weeks.txt文件产生了冲突:

Git命令中的restore是什么?

这时如果使用git status查看weeks.txt文件:

Git命令中的restore是什么?

此时weeks.txt文件的状态就是unmerged paths也就是处于冲突状态。可以使用git restore来选择需要保留的文件版本。比如如果想保留feature中的weeks.txt版本,就可以使用--thiers代表要保留merge的版本。

git restore weeks.txt --theris

执行命令后weeks.txt文件内容变为Tuesday

Git命令中的restore是什么?

如果此时想再切回master版本的代码,可以再使用--ours参数切回master:

git restore weeks.txt --ours

Git命令中的restore是什么?

这个命令跟git checkout weeks.txt --theris或--ours功能一样。

--merge(-m)

该参数可以将已经处理完成的有冲突的文件,重新还原成未处理的产生冲突的状态。

还是以上述的weeks.txt文件为例,此时如果我们已经处理完冲突,就可以使用git add weeks.txt将文件标记为已经处理完冲突。

Git命令中的restore是什么?

但是现在如果想重新回到weeks.txt冲突产生的时候该怎么办呢?使用--merge就能做到:

git restore weeks.txt --merge

使用该命令就可以回滚weeks.txt文件,重新回到master merge feature的状态。

Git命令中的restore是什么?

--conflict=<style>

这个参数和--merge的作用十分相似,只不过它能让冲突以不同方式展现。<style>的默认值是"merge",可配置的值有"diff3"和"zdiff3"。

默认值merge的效果和使用--merge效果一样,直接来看diff3的效果:

在master分支有一个weeks.txt文件内容为Monday。然后使用git switch -c创建一个新的分支feature,然后修改weeks.txt内容为:

Tuesday
Common Change

使用commit提交到本地仓库中并命名为b。这样在feature分支的commit路径为a->b。重新切回master分支修改weeks.txt内容为:

Wednesday
Common Change

然后提交本地仓库并命名为c,在master分支的commit路径就为a->c。接着将feature分至合并到master中:

Git命令中的restore是什么?

这样"Tuesday"和"Wednesday"就产生了冲突,可以使用上述提到git restore或git checkout解决冲突。随后如果想回滚weeks.txt文件重新回到合并的状态,可以使用git restore --conflict=merge或git restore --merge。

但是使用这两个命令都不能知道修改master和feature之前的,也就是它们的共同祖先-名称为"a"的提交记录的文件内容,这时候就可以使用--conflict=diff3。

git restore weeks.txt --conflict=diff3

使用命令后:

Git命令中的restore是什么?

如上图,在文件中出现三条对比路线。ours、共同的祖先||||||| base和theirs。这样就可以清楚对比本地改了哪些内容和即将合并的分支有哪些区别。

值得注意的是此时的共同修改内容"Common Change"在ours和theirs中都存在了,它是一个无需手动解决的冲突,没必要在两个内容块中都展示出来,所以可以使用第二个参数--zdiff3把Common Change的内容提取出来:

Git命令中的restore是什么?

如上图,此时的Common Change被提取到了冲突代码的外面。

--ignore-unmerged

当使用git restore去还原存在冲突且文件是unmerged的状态,那么这个操作不会被中断。

假设项目中有两个分支,一个分支为master,在该分支中有一个weeks.txt文件,文件内容为"Monday Common Change"。

Git命令中的restore是什么?

另一个分支为feature,在该分支有一个weeks.txt文件,文件内容为"Tuesday Common Change"。除了该文件外还有一个fruits.txt内容为"Apple":

Git命令中的restore是什么?

这时候将feature分支合并到master中。

Git命令中的restore是什么?

如上图,fruits.txt文件被add到了缓存区,weeks.txt文件产生了冲突需要处理。

如果这时候直接使用git restore .命令恢复所有文件是会失败的,并且restore操作会被终止。

Git命令中的restore是什么?

这时候就可以加上--ignore-unmerged来忽略有冲突且没有处理的文件(冲突内容会被保留,文件会被标记为已解决):

git restore --ignore-umerged --staged .

Git命令中的restore是什么?

如上图,当前restore操作成功的进行了还原,并且weeks.txt的冲突还是存在,文件也被标记为已解决。

--overlay、--no-overlay

在使用overlay模式时,如果在还原目标中不存在该文件,那么在当前的还原位置也不会直接删除该文件。默认使用的是--no-overlay,也就是和上面的条件相反会删除不存在的文件。

假设项目中有一个master分支,分支有两个文件,第一个是weeks.tx内容是"Monday",第二个文件是fruits.txt内容是"Apple"。

项目中还有另一个feature分支,分支有一个文件weeks.txt内容是"Tuesday"。

现在如果想让master分支还原feature分支的所有文件,可以使用git restore命令来还原:

git restore . --source feature

还原结果:

Git命令中的restore是什么?

如上图,weeks.txt文件被成功还原成了feature分支中的内容,但是fruits.txt文件却被删除了。如果想同时保留fruits.txt文件不被删除就可以使用--overlay:

git restore . --source feature --overlay

还原结果:

Git命令中的restore是什么?

这时候fruits.txt文件被成功的保留了下来,并且weeks.txt被还原成了feature中的文件内容。

<pathspec>

--ignore-skip-worktree-bits

稀疏检出模式中,默认只会更新和<pathspec>匹配的条目以及$GIT_DIR/info/sparse-checkout中声明的条目。这个选项将会忽略稀疏模式,无条件的恢复和<pathspec>匹配的条目。

--recurse-submodules、--no-recurse-submodules

如果使用**--recurse-submodules**选项,在还原工作区内容的同时还会同步还原submodules中的内容。如果没有指定该参数或者使用--no-recurse-submodules,submodules将不会进行还原,就像是使用git-checkout命令一样,HEAD指针将会和submodules进行分离。

假设项目中有该如下结构:

Git命令中的restore是什么?

如上图,项目中有一个submodule,在submodule里面有一个README.md文件且没有任何内容。接下来随便写点内容做一些更改:

Git命令中的restore是什么?

此时在README.md文件中增加了"project init",如果这时候使用restore命令进行还原:

git restore .

项目中的submodules是不会有任何变化的:

Git命令中的restore是什么?

在执行命令后,README.md文件还是和修改后的样子一样。如果想要进行还原submodule则可以使用--recurse-submodules选项:

git restore . --recurse-modules

执行命令后:

Git命令中的restore是什么?

README.md文件成功变成了上一次commit的状态。

--pathspec-from-file=<file>

在上面指定restore的<pathspec>都是来自命令行参数,然而也可以指定它来自某一个文件。

假设项目中有如下结构:

Git命令中的restore是什么?

a.txt、b.txt和c.txt的内容都是空的,然后在pathspec.txt中写入要还原的:

a.txt
b.txt

如上所示,目标是还原a.txt和b.txt,而c.txt不进行还原。多个使用换行符进行分隔,每一行都是一个独立的。

然后在a.txt、b.txt以及c.txt分别写入各自的文件名称字符串:

Git命令中的restore是什么?

最后尝试使用resotre命令进行还原:

git restore --pathspec-from-file=pathspec.txt

执行命令后:

Git命令中的restore是什么?

如图所示,a.txt和b.txt被成功还原了,但是c.txt还是保持修改后的样子,这说明--pathspect-from-file选项生效了。

--pathspec-file-nul

只有和--pathspec-from-file才有意义,指定使用NUL字符作为分隔符而不是换行符。

转载自:https://juejin.cn/post/7193648925000073274
评论
请登录