在不签出的情况下将文件提交到不同的分支

Posted

技术标签:

【中文标题】在不签出的情况下将文件提交到不同的分支【英文标题】:Commit a file to a Different Branch Without Checkout 【发布时间】:2011-12-17 11:55:32 【问题描述】:

是否可以在不签出该分支的情况下在 git 分支中提交文件?如果有怎么办?

基本上我希望能够在我的 github 页面分支中保存文件,而无需一直切换分支。有什么想法吗?

更新:不可能做我想做的事(请参阅下面的 cmets 以了解用例)。我最终做的是以编程方式将我的当前目录克隆到 tmp 目录,然后检查我在该 tmp 目录中的分支(不影响我的工作目录)并将我的文件提交到 tmp 目录克隆。完成后,我推回我的工作目录并删除 tmp 目录。糟透了,但这是在不更改工作目录的当前工作分支的情况下将文件提交到另一个分支的唯一方法。如果有人有更好的解决方案,请随时在下面添加。如果它比“无法完成”更好,我会接受你的。

【问题讨论】:

你为什么不想结帐另一个分支?是因为您有未提交的更改吗? @gustavotkg 我正在以编程方式将文件同步到后台的 github pages 分支。我需要能够在不签出分支的情况下这样做,因为签出分支会影响当前用户的工作分支。现在我正在做一个复杂的克隆和临时目录的舞蹈,当我真正想做的只是添加一个文件而不影响用户的当前分支时。 一个用例是,我发现了一个错误,做了一个快速修复,但想将它推送到 develop 而不是当前的问题分支。至少这就是我错过该功能的原因;) @KingCrunch 你可以使用git stashgit checkout develop,修复你的错误,git commitgit checkout - 然后git stash pop,这样你就可以恢复你的更改 在克隆中做某事没有错;如果它们是本地的,它将使用硬链接,除了额外的工作树副本之外,您甚至不会占用额外的磁盘空间。如果您不想推/拉,您可以使用git-new-workir 让两个工作树通过 .git 中的符号链接共享存储库。 【参考方案1】:

这是不可能的。

您提交的更改与当前工作副本相关。如果您想提交到另一个分支,这意味着您可以从工作副本提交更改,但基于另一个副本状态进行更改。

这不是对您的工作进行版本控制的自然方式,这就是为什么您需要执行不同的步骤(存储更改、签出分支、弹出存储和提交)来完成它。

对于您的具体用例,一种简单的方法是保留两份工作副本,一份在master 分支签出,另一份在pages 分支签出。

pages 工作副本中,将master 副本添加为远程存储库。

您在master 上提交页面 从master 拉到pages 副本 推送到 GitHub 将主分支重置为之前的状态。

【讨论】:

可以在不签出的情况下从不同的分支读取文件,所以我希望有一种方法可以在不签出的情况下写入不同的分支。虽然我知道这不是“自然的”,但在这种情况下,我使用 GIT 作为数据存储而不是纯粹的版本控制。我有一个解决方法(请参阅我上面的评论),但它相当粗糙且容易出错。 只是好奇:为什么要维护一个单独的页面分支? pages.github.com 这是一种直接使用 github 提供文档或演示页面等 html 内容的方法。它必须在一个单独的分支中,因为这就是 github 编写它的方式。虽然数据(在这种情况下为文档)与项目相关,但由于多种原因,它不应该在主分支中,因为与代码无关的更改是其中之一,压倒了 master 的提交消息。 查看下方 Charles Bailey 的回答! 这个答案如何正确?您提交的更改与您的工作副本无关。它们完全基于添加到索引中的内容。您可以删除工作树中的所有内容,但仍然 git commit 就好像什么都没发生一样,因为 git 只查看索引。【参考方案2】:

只要您当前的索引中没有任何与您想要保留的HEAD 不同的内容,您就可以这样做。 (如果您确实想保留索引,可以临时导出 GIT_INDEX_FILE 环境变量以在这些命令执行期间指向临时文件。)

# Reset index and HEAD to otherbranch
git reset otherbranch

# make commit for otherbranch
git add file-to-commit
git commit "edited file"

# force recreate otherbranch to here
git branch -f otherbranch

# Go back to where we were before
# (two commits ago, the reset and the commit)
git reset HEAD@2

我们从未真正检查过otherbranch,我们的工作树文件也没有被触及。

【讨论】:

我想知道这是否解决了 why 你不想签出另一个分支(或克隆 repo 的副本)的相关问题:假设你有一个非常大型 repo 和“otherbranch”与您当前的分支大不相同(但仍然相对接近合并基础),您想为其添加一个小修补程序。检查其他分支,进行更改并返回您的工作分支可能会使您倒退相当长的一段时间。假设这种方法中提出的 reset --mixed 比检查一个非常旧的分支要快得多,这是否正确? 这确实应该是公认的答案。今天我不得不从一年前的标签创建一个分支,然后向它添加一个提交。我测试了两种方式。该分支与我现在的主管相差 72,000 个文件,我花了 2.5 分钟结帐。 (返回只用了 1.5 分钟。)然后我尝试了你的方式,每次只用了大约 15 秒,其中大部分时间是打印文件列表所花费的时间。对于较大的存储库、文件大小和/或速度较慢的硬盘驱动器(我的是快速 SSD),差异可能会更大。这是一个很好的解决方案!【参考方案3】:

可以通过重新实现 git commit 来完成。

这可以通过对git hash-object的各种调用来完成

但这很难实现。

请阅读progit chapter 9 了解更多详细信息以及如何模拟提交的完整示例。

【讨论】:

【参考方案4】:

正如其他几个人所说,这实际上是可能的,但不切实际。

然而,从 Git 2.5 开始(2.6 中有一些重要的修复,此后还有一些次要修复),有一个实用的方法可以使用git worktree add 来做到这一点。

例如,假设您要“同时”处理分支 maindoc,或“同时”处理分支 developtest,但是这两个分支有问题故意包含不同的东西。 (例如,doc 分支的文档存在于代码之外或旁边,或者test 分支具有将针对代码运行但未分发的测试,或者预计会出现测试失败的测试故意跳过develop 一侧,或其他。)

不仅仅是:

git clone -b develop <source> theclone

接着在theclone 工作,不断在两个分支之间来回切换,你会:

git clone -b develop <source> theclone

然后:

cd theclone
git worktree add ../ct test  # check out branch test in ../ct

或者只是:

git worktree add ../test     # check out branch test in ../test

现在您可以在 ../test 中运行测试,同时在 theclone 中进行开发。您可以以通常的方式将更改从一个分支合并和/或变基到另一个分支:底层存储库已经共享,因此不需要git pushgit fetch。您只需将 两个 分支签出到两个单独的工作树中,从顶层命名为 theclonetest

【讨论】:

【参考方案5】:

我做了一个小工具来做这个:https://github.com/qwertzguy/git-quick

它可以让您编辑来自另一个分支的特定文件,而无需完全检查另一个分支(只是您要编辑的文件)并提交它们。所有这些都不会影响您的工作副本或暂存区域。

在幕后它使用了 git worktree 和稀疏结帐的组合。出处比较小,可以通读。

【讨论】:

【参考方案6】:

虽然目前没有单个命令可以执行此操作,但至少有两个其他选项。

    您可以使用 github api 来创建提交。 This post 详细说明在 github 存储库中创建提交。

    Create github pages as a submodule.

    使用一系列管道命令来创建提交。 git book has a description 用于创建提交的管道命令

注意:命令现在是 mktree 而不是 mk-tree

【讨论】:

【参考方案7】:

我不同意这是不可能的。通过混合git stash pushgit stash popgit checkoutgit checkoutgit addgit commit 这是可能的。

我如何理解问题:

您在 master 分支上,您对文件 patched.txt 进行了一些修改,并且您希望将此文件提交到其他分支。

你想做的是:

通过执行 git stash 保存此 repo 中的所有更改 从隐藏的堆栈中结帐 file.txt 将文件patched(仅此文件)添加到新分支 在修改file.txt之前回到repo状态

这可以通过执行以下命令来实现:

destBranch=patch
thisBranch=master
FileToPutToOtherBranch="file1.txt file2.txt 'file with space in name.txt'"
message="patched files $FileToPutToOtherBranch"
                                                                                  #assumption: we are on master to which modifications to file.txt should not belong
git stash &&\                                                                     #at this point we have clean repository to $thisBranch
git checkout -b $destBranch &&\           
git checkout stash@0 -- $FileToPutToOtherBranch &&                              #if there are many files, repeat this step                                         #create branch if does not exist (param -b)
git add $FileToPutToOtherBranch &&\                                               # at this point this is equal to git add . --update
git commit -m "$message" &&\
git checkout $thisBranch &&\
git stash apply &&\                                                               # or pop if want to loose backup
git checkout $thisBranch -- $FileToPutToOtherBranch                               # get unpatched files from previous branch

我之所以用“&&”结尾是因为如果有人将这个sn-p复制&粘贴到终端,即使出现一个错误,也会执行下一个命令,这不好。 \ 用于通知 shell 命令在下一行继续。

为了证明这个工作我提供了这个sn-p的测试环境

mkdir -p /tmp/gitcommitToAnyBranch && cd /tmp/gitcommitToAnyBranch &&\
git init 
echo 'this is master file' > file1.txt
echo 'this is file we do not want to have modified in patch branch because it does not     patches any feature' > docs.txt
git add file1.txt && git commit -m "initial commit" 
echo 'now this file gets patched' > file1.txt
git status

现在,如果您使用参数运行我的脚本

destBranch=patch
thisBranch=`git rev-parse --abbrev-ref HEAD`
FileToPutToOtherBranch="file1.txt"
message="patched file $FileToPutToOtherBranch"

file1.txt 只能在补丁分支中修改,更多信息请参见gitk --all

【讨论】:

问题标题为Without Checkout。这个答案使用checkout【参考方案8】:

这里的大多数答案都说它无法完成,或者使用存储。它可以在不隐藏的情况下完成,但这并不容易,而且我认为这不是你想要融入日常生活的东西。

对于这个例子,我从一个主分支和一个 foo 分支开始。

o--o--o (main)
    \
     o (foo)

我想在 foo 签出时向 main 提交一个文件。我这里写的命令都是在没有检查main的情况下完成的。

git add examplefile
git write-tree  
    # this returns a hash: 2ceb75e10358ba27aaf2291e4e302d759fbedd55
git commit-tree 2ceb75e10358ba27aaf2291e4e302d759fbedd55 -p main -m "commit message"  
    # this makes a commit with commit the main branch points to as parent
    # it too returns a hash: 56823299cdc8b6c1ab30095f6b2c2143f3bb2122
git branch -f main 56823299cdc8b6c1ab30095f6b2c2143f3bb2122
    # this points the main branch to the new commit

那么我们做了什么?本质上,我们完成了普通添加提交例程所做的所有步骤,并进行了一些调整。

首先,我们像往常一样将文件添加到暂存区。接下来,我们运行git write-tree。这会将暂存区域写入一棵树并返回该树的哈希值。提交只不过是一个指向树的指针、一个指向其父级的指针和一条消息。

然后,我们实际上是通过给它所需的三样东西来进行提交:树、父(主)和提交消息。请注意,在我的计算机上,此命令不会打开提交消息的编辑器,因此我需要在命令行中提供它。您的体验可能会有所不同。

情况如下:

        o (new commit)
       / 
o--o--o (main)
    \
     o (foo)

我们在正确的位置进行了新的提交。但是,它是悬空的,这意味着没有分支(或标签)指向它。我们现在必须将 main 指向新的提交。

最后一个命令正是这样做的。它采用新创建的提交的哈希并将 main 指向它。

完成!

正如我在一开始所说的:这不是你想要经常做的事情。正常的 add-commit 策略是经过深思熟虑的。这种策略可能会搞砸一些你不知道的事情。它不检查冲突,它只是覆盖文件而不提及它。

终于,一举一动搞定一切的单线:

git branch -f main $(git commit-tree $(git write-tree) -p main -m "commit message")

write-tree 的哈希输出立即用作 commit-tree 的输入,其输出立即用于移动分支。

【讨论】:

【参考方案9】:

如果你不小心在错误的分支中修改了东西,这里有一些简单的步骤:

    提交这些更改; 将它们合并到正确的分支中; 首先检查您所在的分支并将其重置为之前的提交; 使用“git checkout -- .”清理您的修改。

之后一切都会好起来的。您还可以有选择地合并、重置和清理您的修改。

【讨论】:

【参考方案10】:

不,你不能,但是你通过硬拷贝文件所做的事情是由git stash 使用提交实现的。 stash 用作保存“分支空间之外的某处”的更改堆栈,因此可用于在分支之间移动修改。

&lt;file&gt; 上进行更改 git 添加&lt;file&gt; git 存储 git 结帐&lt;branch you want to commit&gt; git stash pop git commit -m "&lt;commit message&gt;"

如果你经常这样做,你可能可以把这些简单的命令写下来。

【讨论】:

【参考方案11】:

这就是我的做法:

如果我已经严重提交,回滚一次提交:

git reset --soft 'HEAD^'

添加要添加的文件

git add .

创建一个新的临时分支:

git checkout -b oops-temp

提交您的更改:

git commit -m "message about this commit"

签出您要签出的分支:

git checkout realbranch

合并旧分支:

git merge oops-temp

删除旧分支:

git branch -D oops-temp

【讨论】:

以上是关于在不签出的情况下将文件提交到不同的分支的主要内容,如果未能解决你的问题,请参考以下文章

如何挑选多个提交

如何在不采用其中更改的情况下将 git 分支声明为合并?

如何在不知道 JGit 本地是不是存在的情况下签出远程分支?

在不使用 Apple Developer Portal 的情况下将 UDID 添加到配置文件

使用git提交代码--在不切换分支的情况下向不同的分支提交代码

如何在不先加载到 RAM 的情况下将文件加载到 blob 中?