Reputation: 2857
While working on some feature I created a lot of changes but I don't want to push all of them at once. I want to push them feature by feature. How to select files while creating a new branch?
Update
Suppose I have a branch A and it has three files in it namely a, b, c. I have committed all the changes over a period of time. Now I want that the changes in file a should go in a new branch similarly changes in file b goes in another branch etc. Is there any way to do that? I think it will be relative to some commit.
I tried searching but couldn't find any answer?
Upvotes: 0
Views: 1992
Reputation: 5027
First of all, before attempting anything, you should make a safe copy of your entire repo folder. The following operations are destructive, so if you miss something somewhere, you take the risk to lose a part of your work.
Second, the following operations shouldn't take place on a local repo that has already been pushed, since they are destructive. (In the case the changes are pushed already, you should first create a branch that will be a copy of your work and then apply this method on the copied branch. Can be done using git rebase
)
Third, it is not sure you really want to do that. There may be another way to deal with your situation. Read everything throughout and judge for yourself before starting anything. (And make a copy of your repo otherwise a forgotten step might let you regret not to have a copy).
Now, say you want to do that, here's how: the idea is to 'rewind' the commits until the first commit you want to start from, using git reset
s and git stash
es. There, create the branches dedicated to the files. Then, visit each branch one after the other, stash pop
and each time, commit only the desired file. Once done for the first commit, do the same again with the second one, then the third one etc. Only problem, after the first one, it will create conflicts. They can be solved easily in a dirty way...
So here starts an example from scratch:
Will be a little bit different than the second case (all files will be committed in the same commits):
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|✔] $
Picture of the current situation:
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] $
Using git stash pop
to unstack the last stash (so, the first commit with all three files). Then commit file_a, and restash the rest.
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] $
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] $
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.
This creates a conflict since file_b and file_c do not exist any more in branch A. We can just dirtily git rm
them.
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'
And now, commit file_a, alone:
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(+)
Short check everything is as expected:
local_repo_test [Branch_A|⚑ 2] $ ls
file_a initial_situation
local_repo_test [Branch_A|⚑ 2] $ cat file_a
abc
ABC
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] $
Practical thing is, git
does not remove the stash when there has been a conflict, so all three files remain available.
But the last stash is still here, unlike the first one (no conflicts, was removed). So we have to use git stash drop
to remove it:
local_repo_test [Branch_C|⚑ 2] $ git checkout Branch_A
local_repo_test [Branch_A|⚑ 2] $ git stash drop
refs/stash@{0} supprimé (f9c96fb3a25698b15a56fc91a5e57a2b6b54fa75)
Now we can repeat the second step on the last stashed commit:
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(+)
Check everything is as expected:
local_repo_test [Branch_C|⚑ 1] $ ls
file_c initial_situation
local_repo_test [Branch_C|⚑ 1] $ cat file_c
fgh
GHI
789
Remove the last stash:
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|✔] $
Picture of the situation:
I think it's your goal. But this is really a huge hassle to perform this over 10 commits. It's especially difficult because it's easy to make a mistake somewhere, mess up everything and have to start over from the very beginning. (If you really intend to do that, I would advise to save also copies of the currently being transformed repo regularly).
Now, consider this: suppose you have made all your changes. Now you want to work on file_a in branch A. You certainly want to merge the changes into master. Same for file_b and file_c. It's easy:
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|✔] $
Pic of the new situation:
But of course:
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|✔] $
All three files are back in master. In their latest versions.
Of course, you will have only file_a in Branch_A, only file_b in Branch_B and only file_c in Branch_C. This has the advantage of being very clear when you browse your project's directories. But the disadvantages of making it impossible to test the changes in Branch_A (if any feature from file_b or file_c is required); same for branches B and C. You'll have to take care taking everything what is needed for file_a to be tested, in branch A too (same for B and C). And also, if you work on master, you'll have to be careful to commit things in a clever way to be able to cherry-pick what you need in Branch_A (a merge would be better, but will of course put file_b and file_c into Branch_A and all your hassle would have been done in vain).
I don't know if there are any other considerations that make you want to transform your project this way, but maybe you should consider to simply create three branches from where you are and work on file_a only in branch A etc. Even if it means that after your will have merged changes from a branch into master, you'll have to merge master to the other branches to avoid conflicts.
OK, so, provided you committed your changes for files a, b and c in different commits, then you can use git cherry-pick
to help you.
git cherry-pick hash_commit
can take only one commit from a branch to copy it to the branch you're working on.
So, you can go back to a commit that's been made early enough, create the new branch for the file a, checkout this branch, and cherry-pick the commits that are about file a; then the same for file b, file c etc.
Here is an example from scratch (I only removed several useless outputs):
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...]
So at this point, situation is following:
Now let's go back to a commit before the files were even created, using the hash of the matching commit, taken from the git hist
output just above:
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
Let's create a branch for file a and switch to it:
local_repo_test [:38422c2|✔] $ git checkout -b Branch_for_file_a
As you can see there's no file a in this branch yet:
local_repo_test [Branch_for_file_a|✔] $ ls
initial_situation
Now let's cherry-pick the commit about file 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
Check that file a is here now:
local_repo_test [Branch_for_file_a|✔] $ ls
file_a initial_situation
And then the same for file b and file 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|✔] $
After that, all three branches, for files a, b and c have the initial files plus only file_a, file_b or file_c. Master still has everything.
A picture of the current situation (illustrations made using gitg):
Upvotes: 2