如何在git中创建新分支时选择特定文件?

时间:2016-03-10 11:59:39

标签: git version-control

在处理某些功能时,我创建了很多更改,但我不想立刻推送所有这些功能。我想按功能推送它们。如何在创建新分支时选择文件?

更新

假设我有一个分支A,它有三个文件,即a,b,c。我已经承诺了一段时间内的所有变化。现在我希望文件a中的更改应该放在一个新的分支中,同样文件b中的更改进入另一个分支等。有没有办法做到这一点?我认为这与某些提交相关。

我试过搜索但找不到任何答案?

1 个答案:

答案 0 :(得分:2)

第一种情况:你的提交混合了不同的文件:

首先,在尝试任何操作之前,您应该制作整个repo文件夹的安全副本。以下操作具有破坏性,因此如果您遗漏某些地方,您将冒险失去部分工作。

其次,以下操作不应该在已被推送的本地仓库上进行,因为它们具有破坏性。 (如果已经推送了更改,则应首先创建一个将作为工作副本的分支,然后在复制的分支上应用此方法。可以使用git rebase完成)

第三,它不确定你真的想要那样做。可能有另一种方法来处理您的情况。阅读所有内容并在开始之前自己判断。 (复制你的回购,否则一个被遗忘的步骤可能会让你后悔没有副本。)

现在,假设您想要这样做,请按照以下方式:我们的想法是“倒带”。使用git resetgit stash es进行提交直到您要开始的第一次提交。在那里,创建专用于文件的分支。然后,一个接一个地访问每个分支stash pop,每次只提交所需的文件。一旦完成第一次提交,再次使用第二次提交,然后是第三次提交等。只有问题,在第一次提交之后,它将产生冲突。它们可以很容易地以肮脏的方式解决......

所以这里从头开始一个例子:

首先,创建初始情况

与第二种情况略有不同(所有文件都将在同一次提交中提交):

nico@ometeotl:~/temp$ mkdir local_repo_test
nico@ometeotl:~/temp$ cd local_repo_test/
nico@ometeotl:~/temp/local_repo_test$ git init
Dépôt Git vide initialisé dans /home/nico/temp/local_repo_test/.git/
local_repo_test [master|✔] $ touch initial_situation
local_repo_test [master|…1] $ git add .
local_repo_test [master|●1] $ git commit -a -m "Initial commit"
[master (commit racine) 6d5f562] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 initial_situation
local_repo_test [master|✔] $ echo "abc" > file_a
local_repo_test [master|…1] $ echo "def" > file_b
local_repo_test [master|…2] $ echo "fgh" > file_c
local_repo_test [master|…3] $ ls
file_a  file_b  file_c  initial_situation
local_repo_test [master|…3] $ git add .
local_repo_test [master|●3] $ git commit -a -m "Files a, b and c creation."
[master aeccaa2] Files a, b and c creation.
 3 files changed, 3 insertions(+)
 create mode 100644 file_a
 create mode 100644 file_b
 create mode 100644 file_c
local_repo_test [master|✔] $ echo "ABC" >> file_a
local_repo_test [master|✚ 1] $ echo "DEF" >> file_b
local_repo_test [master|✚ 2] $ echo "GHI" >> file_c
local_repo_test [master|✚ 3] $ git commit -a -m "Files a, b and c all changed (1st time)."                                                             
[master 7d50736] Files a, b and c all changed (1st time).
 3 files changed, 3 insertions(+)
local_repo_test [master|✔] $ echo "123" >> file_a
local_repo_test [master|✚ 1] $ echo "456" >> file_b
local_repo_test [master|✚ 2] $ echo "789" >> file_c
local_repo_test [master|✚ 3] $ git commit -a -m "Files a, b and c all changed (2d time)."                                                              
[master 5c698df] Files a, b and c all changed (2d time).
 3 files changed, 3 insertions(+)
local_repo_test [master|✔] $

当前情况图片:

gitg screenshot

然后,'倒带'到我们示例的初始提交:

local_repo_test [master|✔] $ git reset --soft HEAD~1
local_repo_test [master|●3] $ git stash
Saved working directory and index state WIP on master: 7d50736 Files a, b and c all changed (1st time).
HEAD est maintenant à 7d50736 Files a, b and c all changed (1st time).
local_repo_test [master|⚑ 1] $ git reset --soft HEAD~1
local_repo_test [master|●3⚑ 1] $ git stash
Saved working directory and index state WIP on master: aeccaa2 Files a, b and c creation.
HEAD est maintenant à aeccaa2 Files a, b and c creation.
local_repo_test [master|⚑ 2] $ git reset --soft HEAD~1
local_repo_test [master|●3⚑ 2] $ git stash
Saved working directory and index state WIP on master: 6d5f562 Initial commit
HEAD est maintenant à 6d5f562 Initial commit
local_repo_test [master|⚑ 3] $

创建新分支:

local_repo_test [master|⚑ 3] $ git checkout -b Branch_A
local_repo_test [Branch_A|⚑ 3] $ git checkout -b Branch_B
local_repo_test [Branch_B|⚑ 3] $ git checkout -b Branch_C
local_repo_test [Branch_C|⚑ 3] $

提交file_a:

使用git stash pop取消堆叠最后一个存储(因此,第一次提交所有三个文件)。然后提交file_a,并重新设置其余部分。

local_repo_test [Branch_C|⚑ 3] $ git checkout Branch_A
local_repo_test [Branch_A|⚑ 3] $ git stash pop
Sur la branche Branch_A
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)

        nouveau fichier: file_a
        nouveau fichier: file_b
        nouveau fichier: file_c

refs/stash@{0} supprimé (126b2f954385e9c88becc4015f64d95e8647df5e)
local_repo_test [Branch_A|●3⚑ 2] $ git commit file_a -m "File a only commit (1st)"
[Branch_A a096c64] File a only commit (1st)
 1 file changed, 1 insertion(+)
 create mode 100644 file_a
local_repo_test [Branch_A|●2⚑ 2] $ git stash
Saved working directory and index state WIP on Branch_A: a096c64 File a only commit (1st)
HEAD est maintenant à a096c64 File a only commit (1st)
local_repo_test [Branch_A|⚑ 3] $ 

file_b和file_c相同:

local_repo_test [Branch_A|⚑ 3] $ git checkout Branch_B
local_repo_test [Branch_B|⚑ 3] $ git stash list
stash@{0}: WIP on Branch_A: a096c64 File a only commit (1st)
stash@{1}: WIP on master: aeccaa2 Files a, b and c creation.
stash@{2}: WIP on master: 7d50736 Files a, b and c all changed (1st time).
local_repo_test [Branch_B|⚑ 3] $ git stash pop
Sur la branche Branch_B
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)
        nouveau fichier: file_b
        nouveau fichier: file_c
refs/stash@{0} supprimé (1e5fa03fbda1fec0180cbeb66a74a25d8e0ee81f)
local_repo_test [Branch_B|●2⚑ 2] $ git commit file_b -m "File b only commit (1st)"                                                                     
[Branch_B 2b2868b] File b only commit (1st)
 1 file changed, 1 insertion(+)
 create mode 100644 file_b
local_repo_test [Branch_B|●1⚑ 2] $ git stash
Saved working directory and index state WIP on Branch_B: 2b2868b File b only commit (1st)
HEAD est maintenant à 2b2868b File b only commit (1st)
local_repo_test [Branch_B|⚑ 3] $ git checkout Branch_C
Basculement sur la branche 'Branch_C'
local_repo_test [Branch_C|⚑ 3] $ git stash pop
Sur la branche Branch_C
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)
        nouveau fichier: file_c
refs/stash@{0} supprimé (76825e752c810dbce2b8e47611ee20d1d33f8018)
local_repo_test [Branch_C|●1⚑ 2] $ git commit file_c -m "File c only commit (1st)"                                                                     
[Branch_C ddffa92] File c only commit (1st)
 1 file changed, 1 insertion(+)
 create mode 100644 file_c
local_repo_test [Branch_C|⚑ 2] $

现在,回到分支A:

local_repo_test [Branch_C|⚑ 2] $ git checkout Branch_A 
local_repo_test [Branch_A|⚑ 2] $ git stash pop
CONFLIT (modification/suppression) : file_c supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_c laissée dans l'arbre.
CONFLIT (modification/suppression) : file_b supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_b laissée dans l'arbre.

这会产生冲突,因为file_b和file_c在分支A中不再存在。我们可能只是git rm它们。

local_repo_test [Branch_A|●1✖ 2⚑ 2] $ git rm file_b
file_b: needs merge
rm 'file_b'
local_repo_test [Branch_A|●1✖ 1⚑ 2] $ git rm file_c
file_c: needs merge
rm 'file_c'

现在,单独提交file_a:

local_repo_test [Branch_A|●1⚑ 2] $ git commit file_a -m "File a only commit (2d)"                                                                      
[Branch_A c6372c4] File a only commit (2d)
 1 file changed, 1 insertion(+)

简短检查一切是否符合预期:

local_repo_test [Branch_A|⚑ 2] $ ls
file_a  initial_situation
local_repo_test [Branch_A|⚑ 2] $ cat file_a
abc
ABC

现在与file_b和file_c相同:

local_repo_test [Branch_A|⚑ 2] $ git checkout Branch_B
local_repo_test [Branch_B|⚑ 2] $ git stash pop
CONFLIT (modification/suppression) : file_c supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_c laissée dans l'arbre.
CONFLIT (modification/suppression) : file_a supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_a laissée dans l'arbre.
local_repo_test [Branch_B|●1✖ 2⚑ 2] $ git rm file_a
file_a: needs merge
rm 'file_a'
local_repo_test [Branch_B|●1✖ 1⚑ 2] $ git rm file_c
file_c: needs merge
rm 'file_c'
local_repo_test [Branch_B|●1⚑ 2] $ git commit file_b -m "File b only commit (2d)"                                                                      
[Branch_B 51f51c0] File b only commit (2d)
 1 file changed, 1 insertion(+)
local_repo_test [Branch_B|⚑ 2] $ git checkout Branch_C
local_repo_test [Branch_C|⚑ 2] $ git stash pop
CONFLIT (modification/suppression) : file_b supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_b laissée dans l'arbre.
CONFLIT (modification/suppression) : file_a supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_a laissée dans l'arbre.
local_repo_test [Branch_C|●1✖ 2⚑ 2] $ git rm file_a
file_a: needs merge
rm 'file_a'
local_repo_test [Branch_C|●1✖ 1⚑ 2] $ git rm file_b
file_b: needs merge
rm 'file_b'
local_repo_test [Branch_C|●1⚑ 2] $ git commit file_c -m "File c only commit (2d)"                                                                      
[Branch_C 5ecbbbc] File c only commit (2d)
 1 file changed, 1 insertion(+)
local_repo_test [Branch_C|⚑ 2] $ 

中级步骤:

实际情况是,git在发生冲突时不会删除存储,因此所有三个文件仍然可用。

但最后一个藏匿处仍在这里,与第一个不同(没有冲突,被删除)。因此,我们必须使用git stash drop将其删除:

local_repo_test [Branch_C|⚑ 2] $ git checkout Branch_A
local_repo_test [Branch_A|⚑ 2] $ git stash drop
refs/stash@{0} supprimé (f9c96fb3a25698b15a56fc91a5e57a2b6b54fa75)

上次巡演:

现在我们可以在最后一次存储提交中重复第二步:

local_repo_test [Branch_A|⚑ 1] $ git stash pop
CONFLIT (modification/suppression) : file_c supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_c laissée dans l'arbre.
CONFLIT (modification/suppression) : file_b supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_b laissée dans l'arbre.
local_repo_test [Branch_A|●1✖ 2⚑ 1] $ git rm file_b
file_b: needs merge
rm 'file_b'
local_repo_test [Branch_A|●1✖ 1⚑ 1] $ git rm file_c
file_c: needs merge
rm 'file_c'
local_repo_test [Branch_A|●1⚑ 1] $ git commit file_a -m "File a only commit (3rd)"                                                                     
[Branch_A add6af8] File a only commit (3rd)
 1 file changed, 1 insertion(+)

local_repo_test [Branch_A|⚑ 1] $ git checkout Branch_B
local_repo_test [Branch_B|⚑ 1] $ git stash pop
CONFLIT (modification/suppression) : file_c supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_c laissée dans l'arbre.
CONFLIT (modification/suppression) : file_a supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_a laissée dans l'arbre.
local_repo_test [Branch_B|●1✖ 2⚑ 1] $ git rm file_c
file_c: needs merge
rm 'file_c'
local_repo_test [Branch_B|●1✖ 1⚑ 1] $ git rm file_a
file_a: needs merge
rm 'file_a'
local_repo_test [Branch_B|●1⚑ 1] $ git commit file_b -m "File b only commit (3rd)"                                                                     
[Branch_B 1d834c1] File b only commit (3rd)
 1 file changed, 1 insertion(+)

local_repo_test [Branch_B|⚑ 1] $ git checkout Branch_C 
local_repo_test [Branch_C|⚑ 1] $ git stash pop
CONFLIT (modification/suppression) : file_b supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_b laissée dans l'arbre.
CONFLIT (modification/suppression) : file_a supprimé dans Updated upstream et modifié dans Stashed changes. Version Stashed changes de file_a laissée dans l'arbre.
local_repo_test [Branch_C|●1✖ 2⚑ 1] $ git rm file_a
file_a: needs merge
rm 'file_a'
local_repo_test [Branch_C|●1✖ 1⚑ 1] $ git rm file_b
file_b: needs merge
rm 'file_b'
local_repo_test [Branch_C|●1⚑ 1] $ git commit file_c -m "File c only commit (3rd)"                                                                     
[Branch_C 194a658] File c only commit (3rd)
 1 file changed, 1 insertion(+)

检查一切是否符合预期:

local_repo_test [Branch_C|⚑ 1] $ ls
file_c  initial_situation
local_repo_test [Branch_C|⚑ 1] $ cat file_c
fgh
GHI
789

删除最后一个存储:

local_repo_test [Branch_C|⚑ 1] $ git stash drop
refs/stash@{0} supprimé (d6e3685765e05c0368c269a4ee1e7442a409898a)
local_repo_test [Branch_C|✔] $ git checkout master
Basculement sur la branche 'master'
local_repo_test [master|✔] $

情况图片:

gitg screenshot

我认为这是你的目标。但这超过10次提交真的是一个很大的麻烦。这一点特别困难,因为在某个地方容易犯错误,弄乱一切并且必须从一开始就重新开始。 (如果你真的想这样做,我会建议定期保存当前正在变换的回购的副本。)

现在,请考虑一下:假设您已完成所有更改。现在,您希望在分支A中处理file_a。您当然希望将更改合并到master中。对于file_b和file_c也是如此。这很简单:

local_repo_test [Branch_C|✔] $ git checkout master
local_repo_test [master|✔] $ git merge Branch_A
Mise à jour 6d5f562..add6af8
Fast-forward
 file_a | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 file_a
local_repo_test [master|✔] $ git merge Branch_B
Merge made by the 'recursive' strategy.
 file_b | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 file_b
local_repo_test [master|✔] $ git merge Branch_C
Merge made by the 'recursive' strategy.
 file_c | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 file_c
local_repo_test [master|✔] $ git checkout Branch_A
Basculement sur la branche 'Branch_A'
local_repo_test [Branch_A|✔] $

新情况的图片:

enter image description here

但当然:

local_repo_test [Branch_A|✔] $ checkout master
Basculement sur la branche 'master'
local_repo_test [master|✔] $ ls
file_a  file_b  file_c  initial_situation
local_repo_test [master|✔] $ 

所有三个文件都回到master中。在他们的最新版本中。

当然,Branch_A中只有file_a,Branch_B中只有file_b,Branch_C中只有file_c。当您浏览项目的目录时,这具有非常清晰的优点。但缺点是无法测试Branch_A中的更改(如果需要file_b或file_c中的任何功能);分支B和C也是如此。你必须注意在分支A中测试file_a所需的一切(B和C也是如此)。而且,如果你在掌握工作,你必须小心谨慎地提交一些东西,以便能够在Branch_A中挑选你需要的东西(合并会更好,但当然会把file_b和file_c到Branch_A,你所有的麻烦都是徒劳的。)

我不知道是否有任何其他因素会让您想要以这种方式转换项目,但也许您应该考虑从您所在的位置创建三个分支并仅在分支A等中处理file_a即使这意味着在你将把一个分支的变化合并到一个分支之后,你必须将master合并到其他分支以避免冲突。

第二种情况:您的文件在单独的提交中提交:

好的,如果您在不同的提交中提交了对文件a,b和c的更改,那么您可以使用git cherry-pick来帮助您。

git cherry-pick hash_commit只能从分支机构进行一次提交,将其复制到您正在处理的分支机构。

所以,你可以回到早期提交的提交,为文件a创建新的分支,检查这个分支,并挑选关于文件a的提交;那么文件b,文件c等也一样。

这是一个从头开始的例子(我只删除了几个无用的输出):

创建初始情况

nico@ometeotl:~/temp$ mkdir local_repo_test
nico@ometeotl:~/temp$ cd local_repo_test/
nico@ometeotl:~/temp/local_repo_test$ git init
local_repo_test [master|✔] $ touch initial_situation
local_repo_test [master|…1] $ git add .
local_repo_test [master|●1] $ git commit -a -m "Initial commit"
[master (commit racine) 38422c2] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 initial_situation
local_repo_test [master|✔] $ echo "abc" > file_a
local_repo_test [master|…1] $ echo "def" > file_b
local_repo_test [master|…2] $ echo "fgh" > file_c
local_repo_test [master|…3] $ ls
file_a  file_b  file_c  initial_situation
local_repo_test [master|…3] $ git add .
local_repo_test [master|●3] $ git commit file_a -m "File a commit"
[master 0875f3e] File a commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_a
local_repo_test [master|●2] $ git commit file_b -m "File b commit"                                                                                     
[master e78be70] File b commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_b
local_repo_test [master|●1] $ git commit file_c -m "File c commit"                                                                                     
[master c5a7852] File c commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_c
local_repo_test [master|✔] $ git hist
* c5a7852 2016-03-10 | File c commit (HEAD, master) [Nicolas H...]
* e78be70 2016-03-10 | File b commit [Nicolas H...]
* 0875f3e 2016-03-10 | File a commit [Nicolas H...]
* 38422c2 2016-03-10 | Initial commit [Nicolas H...]

所以在这一点上,情况如下:

gitg screenshot 1

创建分支并将选择的文件挑选到其中

现在让我们在创建文件之前回到提交,使用匹配提交的哈希值,取自上面的git hist输出:

local_repo_test [master|✔] $ git checkout 38422c2
Note: checking out '38422c2'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
  git checkout -b new_branch_name
HEAD est maintenant sur 38422c2... Initial commit

让我们为文件a创建一个分支并切换到它:

local_repo_test [:38422c2|✔] $ git checkout -b Branch_for_file_a

正如您所看到的那样,该分支中还没有文件:

local_repo_test [Branch_for_file_a|✔] $ ls
initial_situation

现在让我们选择提交文件a:

local_repo_test [Branch_for_file_a|✔] $ git cherry-pick 0875f3e
[Branch_for_file_a e84a0f9] File a commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_a

检查文件a是否在此处:

local_repo_test [Branch_for_file_a|✔] $ ls
file_a  initial_situation

然后对于文件b和文件c:

相同
local_repo_test [Branch_for_file_a|✔] $ git checkout 38422c2
Note: checking out '38422c252779858e89c307e0a06534a3e8930e12'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
  git checkout -b new_branch_name
HEAD est maintenant sur 38422c2... Initial commit
local_repo_test [:38422c2|✔] $ git checkout -b Branch_for_file_b
local_repo_test [Branch_for_file_b|✔] $ git cherry-pick e78be70
[Branch_for_file_b 92a782f] File b commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_b
local_repo_test [Branch_for_file_b|✔] $ git checkout 38422c2
Note: checking out '38422c252779858e89c307e0a06534a3e8930e12'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
  git checkout -b new_branch_name
HEAD est maintenant sur 38422c2... Initial commit
local_repo_test [:38422c2|✔] $ git checkout -b Branch_for_file_c
local_repo_test [Branch_for_file_c|✔] $ git cherry-pick c5a7852
[Branch_for_file_c 9ea1e18] File c commit
 1 file changed, 1 insertion(+)
 create mode 100644 file_c
local_repo_test [Branch_for_file_c|✔] $ ls
file_c  initial_situation
local_repo_test [Branch_for_file_c|✔] $ 

之后,文件a,b和c的所有三个分支都有初始文件加上file_a,file_b或file_c。师父仍然拥有一切。

当前情况的图片(使用gitg制作的插图):

gitg screenshot 2