在git 2.13之前,只保存多个已更改的文件中的一个文件

Stash only one file out of multiple files that have changed before git 2.13

如何在我的分支上只存储多个已更改文件中的一个?


您也可以使用git stash save -p"my commit message"。通过这种方式,您可以选择应该添加到存储中的块,也可以选择整个文件。

您将被提示为每个Hunk执行一些操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   y - stash this hunk
   n - do not stash this hunk
   q - quit; do not stash this hunk or any of the remaining ones
   a - stash this hunk and all later hunks in the file
   d - do not stash 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
   k - leave this hunk undecided, see previous undecided hunk
   K - leave this hunk undecided, see previous hunk
   s - split the current hunk into smaller hunks
   e - manually edit the current hunk
   ? - print help


更新以下答案适用于Git 2.13之前的Git。对于Git2.13及更高版本,请查看如何Git存储特定文件?

警告

正如评论中所指出的,这将所有东西都放进了藏匿处,无论是阶段性的还是非阶段性的。保存索引只会在隐藏完成后离开索引。当您稍后弹出隐藏时,这可能会导致合并冲突。

这将存储您以前没有添加的所有内容。只要你想保留的东西,然后运行它。

1
git stash --keep-index

例如,如果要将旧提交拆分为多个变更集,可以使用以下过程:

  • git rebase -i
  • 将一些更改标记为edit
  • git reset HEAD^
  • git add
  • git stash --keep-index
  • 必要时把事情解决。别忘了给git add做些改动。
  • git commit
  • git stash pop
  • 如有必要,从5开始重复。
  • git rebase --continue

  • 由于git从根本上是关于管理所有存储库内容和索引(而不是一个或多个文件),因此git stash处理所有工作目录,这并不奇怪。

    实际上,由于Git2.13(2017年第2季度),您可以使用git stash push存储单个文件:

    1
    git stash push [--] [<pathspec>...]

    When pathspec is given to 'git stash push', the new stash records the modified states only for the files that match the pathspec

    有关更多信息,请参阅"隐藏对特定文件的更改"。

    测试用例是不言而喻的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    test_expect_success 'stash with multiple pathspec arguments' '
        >foo &&
        >bar &&
        >extra &&
        git add foo bar extra &&

        git stash push -- foo bar &&  

        test_path_is_missing bar &&
        test_path_is_missing foo &&
        test_path_is_file extra &&

        git stash pop &&
        test_path_is_file foo &&
        test_path_is_file bar &&
        test_path_is_file extra

    最初的答案(下面,2010年6月)是关于手动选择你想要的藏品。

    casebash评论:

    This (the stash --patch original solution) is nice, but often I've modified a lot of files so using patch is annoying

    Bukzor的回答(upvoted,2011年11月)提出了一个更实际的解决方案,基于git add+git stash --keep-index。去看看,把他的答案投上赞成票,这应该是官方的答案(而不是我的)。

    关于这个选项,chhh在评论中指出了另一个工作流:

    you should"git reset --soft" after such a stash to get your clear staging back:
    In order to get to the original state - which is a clear staging area and with only some select un-staged modifications, one could softly reset the index to get (without committing anything like you - bukzor - did).

    (原始答案2010年6月:手动储存)

    然而,git stash save --patch可以让你实现你想要的部分藏匿:

    With --patch, you can interactively select hunks from in the diff between HEAD and the working tree to be stashed.
    The stash entry is constructed such that its index state is the same as the index state of your repository, and its worktree contains only the changes you selected interactively. The selected changes are then rolled back from your worktree.

    但是,这将保存完整索引(可能不是您想要的,因为它可能包括其他已经索引的文件)和部分工作树(可能看起来像您想要存储的工作树)。

    1
    git stash --patch --no-keep-index

    可能更合适。

    如果--patch不起作用,手动过程可能:

    对于一个或多个文件,中间解决方案是:

    • 在Git回购协议之外复制它们(实际上,Eleotlecram提出了一个有趣的替代方案)
    • git stash
    • 把它们抄回去
    • git stash这一次,只有你想要的文件才被保存起来。
    • git stash pop stash@{1}重新应用所有文件修改
    • 在进行任何本地修改之前,git checkout -- afile将文件重置为头内容

    在这个相当繁琐的过程结束时,您将只有一个或多个文件被保存起来。


    git stash -pgit add -pstash --keep-index配合使用太繁琐时,我发现使用diffcheckoutapply比较容易:

    仅"存储"特定文件/dir:

    1
    2
    git diff path/to/dir > stashed.diff
    git checkout path/to/dir

    然后之后

    1
    git apply stashed.diff


    使用git stash push,如下:

    1
    git stash push [--] [<pathspec>...]

    例如:

    1
    git stash push -- my/file.sh

    自2017年春季发布的Git2.13起,该版本就开始提供。


    假设你有3个文件

    1
    2
    3
    a.rb
    b.rb
    c.rb

    你只想储存b.rb和c.rb,而不是a.rb

    你可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # commit the files temporarily you don't want to stash
    git add a.rb
    git commit -m"temp"

    # then stash the other files
    git stash save"stash message"

    # then undo the previous temp commit
    git reset --soft HEAD^
    git reset

    你完了!Hth.


    另一种方法是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Save everything
    git stash

    # Re-apply everything, but keep the stash
    git stash apply

    git checkout <"files you don't want in your stash">

    # Save only the things you wanted saved
    git stash

    # Re-apply the original state and drop it from your stash
    git stash apply stash@{1}
    git stash drop stash@{1}

    git checkout <"files you put in your stash">

    在我(再一次)来到这一页,不喜欢前两个答案(第一个答案不回答问题,我不太喜欢使用-p交互模式)之后,我想到了这个问题。

    这个想法与@vonc建议使用存储库外的文件时的想法相同,您可以将所需的更改保存到某个位置,删除存储库中不需要的更改,然后重新应用您移出的更改。但是,我把git stash作为"某处"(结果,在结尾还有一个额外的步骤:去掉你放在stash中的cahnges,因为你也把它们移走了)。


    更新(2015年2月14日)-我已经重写了一些脚本,以便更好地处理冲突的情况,现在应该将这些冲突显示为未合并的冲突,而不是.rej文件。

    我经常发现对@bukzor的方法做相反的操作更直观。也就是说,阶段化一些更改,然后只存储那些阶段化的更改。

    不幸的是,Git不提供Git存储——只提供索引或类似的内容,所以我快速编写了一个脚本来实现这一点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #!/bin/sh

    # first, go to the root of the git repo
    cd `git rev-parse --show-toplevel`

    # create a commit with only the stuff in staging
    INDEXTREE=`git write-tree`
    INDEXCOMMIT=`echo"" | git commit-tree $INDEXTREE -p HEAD`

    # create a child commit with the changes in the working tree
    git add -A
    WORKINGTREE=`git write-tree`
    WORKINGCOMMIT=`echo"" | git commit-tree $WORKINGTREE -p $INDEXCOMMIT`

    # get back to a clean state with no changes, staged or otherwise
    git reset -q --hard

    # Cherry-pick the index changes back to the index, and stash.
    # This cherry-pick is guaranteed to succeed
    git cherry-pick -n $INDEXCOMMIT
    git stash

    # Now cherry-pick the working tree changes. This cherry-pick may fail
    # due to conflicts
    git cherry-pick -n $WORKINGCOMMIT

    CONFLICTS=`git ls-files -u`
    if test -z"$CONFLICTS"; then
        # If there are no conflicts, it's safe to reset, so that
        # any previously unstaged changes remain unstaged
        #
        # However, if there are conflicts, then we don't want to reset the files
        # and lose the merge/conflict info.
        git reset -q
    fi

    您可以将上述脚本保存为路径上的某个位置的git-stash-index,然后将其作为git stash索引调用。

    1
    2
    3
    # <hack hack hack>
    git add <files that you want to stash>
    git stash-index

    现在,stash包含一个新条目,该条目只包含您所进行的更改,而您的工作树仍然包含任何未更改。

    在某些情况下,工作树的更改可能取决于索引的更改,因此当您存储索引更改时,工作树的更改会产生冲突。在这种情况下,您将得到可以使用git merge/git mergetool/etc解决的常见未合并冲突。


    因为在Git中创建分支很简单,所以您可以创建一个临时分支并将各个文件检入其中。


    将以下代码保存到一个名为stash的文件中。用法是stash 。参数是文件完整路径的正则表达式。例如,存放a/b/c.txt、stash a/b/c.txtstash .*/c.txt等。

    1
    2
    3
    $ chmod +x stash
    $ stash .*.xml
    $ stash xyz.xml

    要复制到文件中的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #! /usr/bin/expect --
    log_user 0
    set filename_regexp [lindex $argv 0]

    spawn git stash -p

    for {} 1 {} {
      expect {
        -re"diff --git a/($filename_regexp)" {
          set filename $expect_out(1,string)
        }
       "diff --git a/" {
          set filename""
        }
       "Stash this hunk" {
          if {$filename ==""} {
            send"n
    "
          } else {
            send"a
    "
            send_user"$filename
    "
          }
        }
       "Stash deletion" {
          send"n
    "
        }
        eof {
          exit
        }
      }
    }


    只是为了防止您在使用git stash时(并且不要真正使用git stash临时存储它),实际上您可以使用

    1
    git checkout -- <file>

    [注释]

    git stash只是一个快速简单的分支和做事情的替代方法。


    我会用git stash save --patch。我不觉得交互性很烦人,因为在交互性过程中有一些选项可以将所需的操作应用于整个文件。


    VONC将文件复制到git repo外部的"中间"解决方案存在的问题是,您会丢失路径信息,这使得稍后复制大量文件变得有些麻烦。

    a发现更容易使用tar(类似的工具可能会做)而不是复制:

    • tar cvf/tmp/stash.tar path/to/some/file path/to/some/other/file(…等)
    • git签出路径/to/some/file path/to/some/other/file
    • 暂存
    • 焦油xvf/tmp/stash.tar
    • 等(见VONC的"中间"建议)


    使用sourcetree可以通过3个步骤轻松完成。

  • 暂时把你不想藏的东西都交出来。
  • 吉特把其他的都加进去,然后把它藏起来。
  • 通过运行git reset来弹出临时提交,将提交的目标放在临时提交之前。
  • 这一切都可以在sourcetree中几秒钟内完成,您只需单击要添加的文件(甚至是单独的行)。一旦添加,只需将它们提交到临时提交。接下来,单击复选框以添加所有更改,然后单击"存储"以存储所有内容。当隐藏的更改被排除在外时,浏览提交列表并在临时提交之前记录提交的哈希值,然后运行"git reset hash_b4_temp_commit",这基本上类似于"popping"提交,方法是在提交之前将分支重置为提交。现在,你只剩下那些你不想藏起来的东西了。


    有时候我在提交分支之前对它做了一个不相关的更改,我想将它移动到另一个分支并单独提交(如master)。我这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    git stash
    git checkout master
    git stash pop
    git add <files that you want to commit>
    git commit -m 'Minor feature'
    git stash
    git checkout topic1
    git stash pop
    ...<resume work>...

    注意:第一个stashstash pop可以取消,您可以在结帐时将所有更改转移到master分行,但只有在没有冲突的情况下。另外,如果您正在为部分更改创建一个新的分支,那么您将需要隐藏。

    您可以简化它,假设没有冲突,也没有新的分支:

    1
    2
    3
    4
    5
    git checkout master
    git add <files that you want to commit>
    git commit -m 'Minor feature'
    git checkout topic1
    ...<resume work>...

    甚至不需要藏匿…


    这里的每个答案都很复杂…

    把这个"藏起来"怎么样?

    1
    2
    git diff /dir/to/file/file_to_stash > /tmp/stash.patch
    git checkout -- /dir/to/file/file_to_stash

    要弹出文件更改,请执行以下操作:

    1
    git apply /tmp/stash.patch

    与保存一个文件并将其弹出完全相同的行为。


    我已经回顾了这方面的答案和评论以及一些类似的主题。请注意,为了能够存储任何特定的跟踪/未跟踪文件,以下命令都不正确:

    • git stash -p (--patch):手工选择hunks,不包括未跟踪的文件
    • git stash -k (--keep-index):将所有跟踪/未跟踪的文件存放在工作目录中
    • git stash -u (--include-untracked):存储所有跟踪/未跟踪的文件
    • git stash -p (--patch) -u (--include-untracked)命令无效

    目前,能够存储任何特定跟踪/未跟踪文件的最合理方法是:

    • 暂时提交不想保存的文件
    • 添加和隐藏
    • 弹出临时提交

    我在另一个问题的答案中为这个过程编写了一个简单的脚本,这里有在sourcetree中执行这个过程的步骤。


    解决方案

    局部变化:

    • 文件(已修改)未暂存
    • 文件_B(已修改)未暂存
    • 文件c(已修改)未暂存

    要创建一个只有文件更改的存储"我的存储",请执行以下操作:

    1
    2
    3
    4
    1. git add file_C
    2. git stash save --keep-index temp_stash
    3. git stash save my_stash
    4. git stash pop stash@#{1}

    完成。

    解释

  • 将文件添加到临时区域
  • 创建一个名为"临时存储"的临时存储并将更改保存在文件中
  • 创建通缉的藏匿("我的藏匿"),只有文件中的更改
  • 在本地代码上应用"临时存储"(文件a和文件)中的更改并删除存储
  • 您可以在步骤之间使用git状态来查看发生了什么。


    如果不想指定包含隐藏更改的消息,请在双破折号后传递文件名。

    1
    $ git stash -- filename.ext

    如果这是一个未跟踪/新文件,你必须先准备好它。

    此方法适用于Git版本2.13。+


    1
    2
    3
    4
    5
    git add .                           //stage all the files
    git reset <pathToFileWillBeStashed> //unstage file which will be stashed
    git stash                           //stash the file(s)
    git reset .                         // unstage all staged files
    git stash pop                       // unstash file(s)


    在这种情况下,我把剩下的东西藏起来。


    当您尝试在两个分支之间切换时,就会出现这种情况。

    尝试使用"git add filepath添加文件。

    稍后执行此行

    git stash --keep-index


    要存储单个文件,请使用git stash --patch

    提示:Stash this hunk [y,n,q,a,d,j,J,g,/,e,?]? ?。只需输入a(把这个混蛋和所有后来的混蛋都藏在文件里),你就没事了。


    我不知道如何在命令行上执行,只使用sourcetree。假设您已经更改了文件A,并且在文件B中有两个更改块。如果您只想将第二个更改块存储在文件B中,而不影响其他所有内容,请执行以下操作:

  • 舞台上的一切
  • 对工作副本执行更改,以撤消文件A中的所有更改(例如,启动外部差异工具并使文件匹配)。
  • 使文件B看起来好像只应用了第二个更改。(例如,启动外部差异工具并撤消第一次更改。)
  • 使用"保留阶段性更改"创建隐藏。
  • 把所有东西都拆下来
  • 完成!

  • 类似情况。我已经承诺并意识到这不好。

    1
    2
    git commit -a -m"message"
    git log -p

    根据答案,这对我有帮助。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # revert to previous state, keeping the files changed
    git reset HEAD~
    #make sure it's ok
    git diff
    git status
    #revert the file we don't want to be within the commit
    git checkout specs/nagios/nagios.spec
    #make sure it's ok
    git status
    git diff
    #now go ahead with commit
    git commit -a -m"same|new message"
    #eventually push tu remote
    git push

    我找不到我需要的答案,这就像:

    1
    2
    3
    4
    git add -A
    git reset HEAD fileThatYouWantToStash
    git commit -m"committing all but one file"
    git stash

    这只存储了一个文件。


    一个复杂的方法是首先提交所有内容:

    1
    2
    git add -u
    git commit // creates commit with sha-1 A

    重置回原始提交,但从新提交签出一文件:

    1
    2
    git reset --hard HEAD^
    git checkout A path/to/the_one_file

    现在,您可以存储一个文件:

    1
    git stash

    通过在重置回原始提交时保存文件系统中提交的内容进行清理:

    1
    2
    git reset --hard A
    git reset --soft HEAD^

    是的,有点尴尬…


    快速回答

    要在git中恢复特定的更改文件,可以执行以下操作:

    1
    git checkout <branch-name> -- <file-path>

    下面是一个实际的例子:

    1
    git checkout master -- battery_monitoring/msg_passing.py