Git11-储藏和引用日志

1、储藏

  • 在日常开发周期中,当要经常中断(例如修复bug、处理来自同事或经理的请求)你正在进行中的工作,你是否感到不堪重负?如果是这样,那么储藏(stash)就是来帮助你的!
  • 储藏可以捕获你的工作进度,允许你保存工作进度并且当你方便时再回到该进度。
    • 当然,你也可以通过Git提供的分支及提交机制来实现该功能。
    • 但是,储藏是一种快捷方式,它让你仅通过一条简单的命令就全面彻底地捕获工作目录索引的当前状态。

示例1-1:

1、创建一个新版本库

//(1)添加用户配置
]# git config --global user.email "hengha@123.com"
]# git config --global user.name "heng ha"
   
//(2)初始化一个新的版本库
]# mkdir stash-example
]# cd stash-example/
]# git init

2、在master分支中创建两个提交

//(1)创建一个提交
]# echo "Line 1 master" >> file1
]# git add file1
]# git commit -m "add 1 to file1"

//(2)创建一个提交
]# echo "Line 2 master" >> file1
]# git add file1
]# git commit -m "add 2 to file1"

3、在dev分支中创建两个提交

//(1)创建并切换分支
]# git checkout -b dev

//(2)创建一个提交
]# echo "Line 1 dev" >> file1
]# git add file1
]# git commit -m "add 1 to file1"

//(3)创建一个提交
]# echo "Line 2 dev" >> file1
]# git add file1
]# git commit -m "add 2 to file1"

1.1、中断工作,并进行储藏

示例1-2:

//(1)切换到master分支
]# git checkout master

//(2)修改文件
]# echo "Line 3 master" >> file1

//(3)进行储藏
]# git stash
Saved working directory and index state WIP on master: f9833c1 add 2 to file1
  • git stash命令的默认可选操作是save。在保存储藏时Git还提供了一条默认日志消息,也可以手动提供一条日志消息,以便帮你更好地回忆你之前在做什么。
    • git stash等价于git stash save "WIP on master: f9833c1 add 2 to file1"。WIP是“进行中的工作”(workin progress)的缩写。
  • 也可以使用Git的其他基础命令来达到相同的效果,需要手动创建一个新分支,在新分支上提交所有修改,之后回到之前的分支继续工作,最后把你保存的分支状态恢复到新的工作目录。
  • git stash save命令会将索引工作目录当前状态(可能被修改了)保存到refs/stash分支中(另存为独立且正常的提交),然后会清除之前的修改以便再次匹配当前分支的HEAD头。
//可以通过查询分支状态获取储藏
]# git show-branch --more=5 stash
[stash] WIP on master: f9833c1 add 2 to file1
[stash^2] index on master: f9833c1 add 2 to file1
[stash^] add 2 to file1
[stash~2] add 1 to file1

1.2、还原储藏

示例1-3:

//还原储藏
]# git stash pop
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   file1
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (5831210c2f2af46264db25bef79cfbda2871236b)
  • 通过git stash save和git stash pop两条命令,实现了储藏状态栈
  • 允许在中断工作流的情况下再次中断!栈上每个储藏的上下文都可以通过正常提交流程来单独管理。
  • git stash pop命令将最近一次save操作保存的内容还原到当前工作目录和索引中。即pop操作会取出储藏的内容,并合并到工作目录和索引中,而不仅仅是覆盖或替换文件。
  • 只能在干净的工作目录中使用git stash pop命令。即便如此,也不能保证能够还原到之前保存时的状态。因为保存的上下文可以应用到不同的提交上,所以可能需要进行合并,并要用户来解决冲突。
  • 在一个pop操作成功后,Git会自动删除储藏状态栈中保存的状态。也就是说,一旦应用,储藏的状态将会丢弃。但是,当需要解决冲突时,Git不会自删除状态,以防你想要尝试不同的方法或还原到不同的提交。一旦你解决了合并冲突并希望继续,你就应该使用git stash drop将状态从储藏栈中删除。否则,Git将维持一个内容不断增加的栈。
  • 如果你只是想应用一个保存在储藏状态栈中的上下文到某个提交,但又不想把它从栈中删除,可以使用git stash apply
    • 在将储藏栈中的一个上下文删除之前,可以使用git stash apply来将它应用到几个不同的提交中。

1.3、在脏的树中进行拉取

  • git stash的另一个经典应用场景是“在脏的树中进行拉取(pull into adirty tree)”。
  • 它大概是这样子的:你在本地版本库中进行开发,并且已经做出多次提交。但仍然有一些尚未提交的修改,突然你发现上游(远程)版本库中有你想要的更新。但如果某些修改与上游有冲突,git pull将会失败,拒绝覆盖本地变更。这时,使用git stash可以快速解决这个问题。
  • 这种情况下,可以使用git stash命令的如下选项将尚未提交的修改迁移走
    • -u, --include-untracked:(仅对push和save有效),将所有未跟踪的文件存储起来。
    • -a, --all将所有被忽略和未跟踪的文件存储起来。
    • -p, --patch:选择想要储藏的文件。
//(1)从上游版本库中拉取代码
git pull
...因为有合并冲突所以拉取失败...

//(2)储藏修改,以获取干净的工作目录
git stash save

//(3)再次从上游版本库中拉取代码
git pull

//(4)还原储藏
git stash pop

1.4、其他命令

示例1-4:

//(1)进行一个储藏
]# git stash save "Line 3 master for stash"

//(2)进行一个储藏
]# echo "Line 4 master" >> file1
]# git stash save "Line 4 master for stash"

1.4.1、git stash list

  • git stash list命令按保存时间由近及远的顺序列出储藏栈中的上下文
  • Git会将最新添加的储藏条目编号为0。随着条目的添加,它的编号会递增

示例1-5:

//(3)列出储藏
]# git stash list
stash@{0}: On master: Line 4 master for stash
stash@{1}: On master: Line 3 master for stash

1.4.2、git stash show

  • git stash show命令可以显示给定储藏条目相对于它的父提交的索引和文件变更记录。默认只显示最近的储藏条目,即stash@{0}。
  • 因为形成储藏状态的变更是相对于某个特定的提交的,所以显示的状态是适合于git diff的状态间比较,而不是适合于git log的提交状态序列。因此,git diff的所有选项也适用于git stash show。

示例1-6:

//显示储藏条目和其父提交的差异
]# git stash show -p
diff --git a/file1 b/file1
index 7926744..07b1abd 100644
--- a/file1
+++ b/file1
@@ -1,2 +1,3 @@
 Line 1 master
 Line 2 master
+Line 4 master

1.4.3、git stash branch

  • 有时,储藏你的变更会导致你的分支上出现一个全新的开发序列,并且在最终还原你的储藏状态到所有变更之前时可能没有直接意义。此外,合并冲突可能会导致弹出操作难以进行。然而,你可能仍需要恢复你储藏的内容。在这种情况下,git提供了git stash branch命令来帮助你。
  • git stash branch命令基于储藏条目生成时的提交,会将保存的储藏内容转换到一个新分支
  • 储藏始终会对原始提交进行重组。最后,重组的储藏状态不会自动提交任何变更到新的分支。所有储藏文件的修改(根据需要,还包括索引的变更)都还留在新建并检出的分支上的工作目录中。
  • (1)例如,你在提交C的时候,中断工作并储藏了当时的状态X'。然后又继续工作,并新创建了提交D、E、F。最后还原储藏X'。

  • (2)还原储藏
//创建并检测到新分支,然后将储藏保存到新分支
git stash branch mod

2、引用日志

  • 有时Git会做出一些很神奇的举动而导致用户不知道刚刚发生了什么。有时你可能只是简单地想知道“等下,我现在在哪儿?刚才发生了什么?”。有时候,你做了某个操作然后意识到,“哦,我不该这么做!”但是为时已晚,你已经失去了一个星期工作量的头提交。
  • 不要慌!Git的引用日志已经考虑到了这些情况!使用引用日志可以确保操作会如你预期般地发生在你计划的分支上,并且你有能力恢复丢失的提交以防误入歧途。
  • 引用日志(reflog)记录非裸版本库中分支头的改变。每次对引用的更新(包括对HEAD的),引用日志都会记录这些引用发生了哪些变化。把引用日志当作面包屑轨迹一样指示你和你的引用去过哪里。以此类推,也可以通过引用日志来跟随你的足迹并回溯你的分支操作。
  • 一些会更新引用日志的基本操作包括:
    • 复制
    • 推送
    • 执行新提交
    • 切换或创建分支
    • 变基操作
    • 重置操作
  • 从根本上说,任何修改引用或更改分支头的Git操作都会记录到引用日志
  • 默认情况下,引用日志在非裸版本库中是启用的,在裸版本库中是禁用的。引用日志是由配置选项core.logAllRefUpdates控制的。
//启用引用日志
git config core.logAllRefUpdates true
//禁用引用日志
git config core.logAllRefUpdates false
  • 查看引用日志
]# git reflog show
f9833c1 (HEAD -> master) HEAD@{0}: reset: moving to HEAD
f9833c1 (HEAD -> master) HEAD@{1}: reset: moving to HEAD
f9833c1 (HEAD -> master) HEAD@{2}: reset: moving to HEAD
f9833c1 (HEAD -> master) HEAD@{3}: reset: moving to HEAD
f9833c1 (HEAD -> master) HEAD@{4}: checkout: moving from dev to master
de37570 (dev) HEAD@{5}: commit: add 2 to file1
218d775 HEAD@{6}: commit: add 1 to file1
f9833c1 (HEAD -> master) HEAD@{7}: checkout: moving from master to dev
f9833c1 (HEAD -> master) HEAD@{8}: commit: add 2 to file1
c14a79c HEAD@{9}: commit (initial): add 1 to file1
  • 每一行都记录了引用历史记录中的单次事务,从最近的变更开始倒序显示。
    • 左一列是发生变更时的提交ID。
    • 左二列中形如HEAD@{6}的条目是每个事务的提交的别名。HEAD@{0}是最新的条目,HEAD@{1}记录HEAD上次的改变,依此类推。
    • 冒号后面是对发生事务的描述。
    • 最后,对事务都会有一个时间戳(未显示),记录该事件在你的版本库中是何时发生的。
  • Git针对引用支持大量基于日期的限定符。其中包括如yesterday、noon、midnight、tea、星期、月份,A.M.和P.M.标识,这些绝对时间或日期,还有像last monday、1 hour ago、10 minutes ago和这些短语的组合(如1 day 2 hours ago等)相对时间或日期。最后,如果省略实际引用名,而只是使用@{...}的形式,那么就默认代表当前分支名。因此,当你在bugfix分支上时,@{noon}代表的就是bugfix@{noon)。
  • 如果Git为版本库中每个引用的每次操作都维护一个事务历史记录,那么引用日志最终不就会变得非常巨大吗?幸运的是,这并不会发生。
    • Git会时不时地自动执行垃圾回收进程。在这个过程中,一些老旧的引用日志条目会过期并被丢弃。通常情况下,一个提交,如果既不能从某个分支或引用指向,也不可达,将会默认在30天后过期,而那些可达的提交将默认在90天后过期。
    • 可以通过版本库中的配置变量gc.reflogExpireUnreachable和gc.reflogExpire进行设置。
    • 可以使用git reflog delete命令来删除单个条目,或使用git reflog expire命令直接让条目过期并被立即删除。
//强制引用日志过期。
git reflog expire --expire=now --all
git gc
  • 储藏和引用日志密切相关。事实上,储藏正是通过使用stash引用的引用日志来实现的。
  • 引用日志都存储在.git/logs目录下
    • .git/logs/HEAD文件包含HEAD值的历史记录,
    • .git/logs/refs/包含所有引用的历史记录,其中也包括储藏。它的二级子目录.git/logs/refs/heads包含分支头的历史记录。
  • 在引用日志中存储的所有信息,特别是.git/logs目录下的一切内容,归根结底还是临时的不重要的。删除.git/logs目录或关闭引用日志不会损坏Git的内部数据结构,它只意味着诸如master@{4}这样的引用不会被解析。
  • 相反,启用引用日志会引入指向提交的应用,而那些提交可能以前是不可达的。如果你正在试图清理并压缩你的版本库,删除引用日志可能会移除那些不可达(即无关)的提交。
#                                                                                                                         #
posted @ 2023-03-28 01:02  麦恒  阅读(48)  评论(0编辑  收藏  举报