为什么git stash -p需要很长时间才能开始?

时间:2018-03-07 10:14:10

标签: git git-stash

在我的回购中,cwd = getcwd() chdir('/opt/project') translation_unit = index.parse(source_file, args=args) chdir(cwd) git diff都在不到一秒的时间内快速运行。但是git stash在显示第一个大块之前需要20秒。为什么会这样?

2 个答案:

答案 0 :(得分:2)

随着Git 2.25.2(2020年3月)的改进,该功能将简化代码。
参见discussion

请参见commit 26f924dElijah Newren (newren)(2020年1月7日)。
(由Junio C Hamano -- gitster --commit a3648c0中合并,2020年1月22日)

unpack-trees:如果不需要更新,请提前退出check_updates()

签名人:伊利亚·纽伦

check_updates()有很多代码可以反复检查是否设置了o->updateo->dry_run

(请注意,o->dry_run!o->update,的近义词,但与commit 2c9078d05bf2不同((unpack-trees:将dry_run标志添加到{ {1}}”,2011年5月25日,Git v1.7.6-rc0)。)
实际上,只要条件出现,该功能几乎都会变成无操作状态

unpack_trees_options

被满足。

通过在函数开始时检查此条件来简化代码,当此条件成立时,请做一些相关的事情并尽早返回。

有些事情使转换不太明显:

  • 当不希望进行更新时,check_updates()实际上没有变成无操作的事实可能有点令人惊讶。
    但是,提交33ecf7eb61(使用它们更新工作树后丢弃“ !o->update || o->dry_run ”缓存条目,2008-02-07,Git v1.5.5-rc0)将丢弃未使用的缓存条目放入{ {1}},因此我们仍然需要保留对deleted的呼叫。
    此调用可能属于另一个函数,但肯定是必需的,因为如果删除该调用,测试将失败。
  • 原始文件无条件称为check_updates()
    从技术上讲,提交7847892716remove_marked_cache_entries():介绍remove_scheduled_dirs(),2009-02-09,Git v1.6.3-rc0)应该使该调用成为条件调用,但实际上并不重要因为当跳过对unlink_entry()的所有调用时,unlink_entry()变为空操作。
    因此,我们不需要调用它。
  • schedule_dir_for_removal()上,原始文件会调用remove_scheduled_dirs()两次,围绕一堆跳过的更新。
    (o->dry_run && o->update)的这两个调用相互抵消,因此在git_attr_set_direction()为真时可以忽略,就像在git_attr_set_direction()时一样。
  • 即使在o->dry_run时,该代码也会先前调用!o->updatesetup_collided_checkout_detection()
    但是,这只是一个昂贵的无操作操作,因为report_collided_checkout()仅清除了每个缓存项的o->dry_run标志,并且setup_collided_checkout_detection()报告了设置了哪些标志。
    由于试运行将跳过所有CE_MATCHED调用,因此report_collided_checkout()将永远不会设置,因此不会报告任何冲突。
    由于无论如何我们都无法检测到冲突,因此,跳过冲突检测设置和报告是一种优化。
  • 即使在checkout_entry()时,以前的代码也会调用CE_MATCHEDget_progress()
    这可以显示跳过所有更新花费了多长时间,这有点用处。
    由于我们跳过更新,因此可以跳过显示跳过更新需要多长时间。

答案 1 :(得分:1)

我注意到同样的问题。这至少在一年前开始,但此后一直没有改善。 我还在很大的仓库上使用git。不幸的是,在我的案例中,其中还包含许多二进制数据,因为它只是使用git_svn的SVN存储库的镜像,而我的同事们认为将二进制测试数据放入存储库中是个好主意。

没有答案,只是提示和猜测在哪里搜索:​​

  • 它的最大区别是,在stash -p的情况下调用函数stash_patch。否则为stash_working_tree

  • stash_patch中有称为执行其他git命令的子进程。其中之一是read-tree(请参阅:man git-read-tree)。最终命令如下所示:GIT_INDEX_FILE=index.stash.<PID> git read-tree HEAD。实际上,这没有时间。

  • 下一步是另一个称为GIT_INDEX_FILE=index.stash.<PID> git add--interactive --patch=stash -- <PATH>的子进程– 这是所有读取结果的来源,并且一直占用所有时间。 有趣的是:仅在GIT_INDEX_FILE=index.stash.<PID> git status之后拨打GIT_INDEX_FILE=index.stash.<PID> git read-tree HEADgit add--interactive一样昂贵。实际上,add--interactive是一个实现add -p的perl脚本。我不了解perl,也很难阅读,但可能会以某种方式检查工作目录状态,并使用与git status相同的代码。

  • 基本思想是:

    • 从HEAD创建临时索引
    • 交互式添加对该索引的更改
    • 将更改后的临时索引保存为树状
  • 最昂贵的部分是为了获得工作目录的状态而没有临时索引。为什么这么贵我不知道。可能有一些缓存的数据无效,它必须读取工作副本中的所有文件至少达到一定数量才能与临时索引进行比较,但是要了解这一点,必须更深入地研究git status的内部。

我试图这样测量:

GIT_INDEX_FILE=.git/index.stash.test git read-tree HEAD
GIT_TRACE_PERFORMANCE=/tmp/trace_status GIT_INDEX_FILE=.git/index.stash.test git st .

结果如下:

20:31:20.439868 read-cache.c:2290       performance: 0.000269090 s:  read cache .git/index.stash.test
20:31:20.441368 preload-index.c:147     performance: 0.001419629 s:   preload index
20:32:15.568433 read-cache.c:1605       performance: 55.128484420 s:  refresh index
20:32:15.568611 diff-lib.c:251          performance: 0.000054503 s:  diff-files
20:32:15.568847 unpack-trees.c:1546     performance: 0.000004362 s:    traverse_trees
20:32:15.568868 unpack-trees.c:447      performance: 0.000008189 s:    check_updates
20:32:15.568874 unpack-trees.c:1643     performance: 0.000040807 s:   unpack_trees
20:32:15.568879 diff-lib.c:537          performance: 0.000079322 s:  diff-index
20:32:15.569115 name-hash.c:600         performance: 0.000197074 s:   initialize name hash
20:32:15.573785 dir.c:2326              performance: 0.004883714 s:  read directory 
20:32:15.574904 read-cache.c:3017       performance: 0.001083674 s:  write index, changed mask = 82
20:32:15.575125 trace.c:475             performance: 55.135763475 s: git command: /usr/lib/git-core/git status .
20:32:15.575421 trace.c:475             performance: 55.136831211 s: git command: git st .

我的仓库如下:

>$ du -hd 1
1,1M    ./.idea
74M     ./code
3,0G    ./.git
2,4G    ./test-data
5,5G    .

如果跟踪直接应用于git stash -p,则为类似图片:

20:43:55.968088 read-cache.c:1605       performance: 59.716998605 s:  refresh index
20:43:55.969584 trace.c:475             performance: 59.719061140 s: git command: git update-index --refresh

git update-index --refresh状态的手册页:

USING --REFRESH
       --refresh does not calculate a new sha1 file or bring the index up to date for mode/content changes. But what it does do is to "re-match" the stat information of a file with the index, so that you can refresh the index for a
       file that hasn’t been changed but where the stat entry is out of date.

       For example, you’d want to do this after doing a git read-tree, to link up the stat index details with the proper files.