Can I remove individual files from a git branch without deleting them when merging into another branch?

StackOverflow https://stackoverflow.com/questions/22881397

  •  28-06-2023
  •  | 
  •  

Question

I'm in the middle of a project where there are two main development branches, since the project consists of a library and an implementation of the library, both of which are being developed in parallel in separate branches.

My workflow is that I merge the library branch with --no-ff into the implementation (named core) branch once enough features have been added to library, and the library branch should ideally never see any files from the implementation, because otherwise both branches would be making changes to the same implementation files. Unfortunately, that is not the case since I started the project from an existing source folder which had some files from the library AND a partially started implementation, and I forgot to remove the implementation files when initially splitting the two working branches from master.

Workflow:

$ git checkout library

..make some changes..

$ git commit -am "updated library"
$ git checkout core
$ git merge --no-ff library

At this point I usually have to deal with conflicts that result from the library updates changing the implementation files in the library branch while merging with changes to the core branch, and having the same files updated, even though the core branch has made its own updates to those files.

How can I remove the implementation directory from the library branch without deleting them when I merge them into the implementation branch?

Was it helpful?

Solution

How can I remove the implementation directory from the library branch without deleting them when I merge them into the implementation branch?

You could perform the merge with a --no-commit, restore the files deleted and complete the commit.

See "git merge: Removing files I want to keep!" as a concrete example.

git checkout dev1
git merge --no-commit dev2
git checkout dev1 implementationFile
git add implementationFile
git commit

OTHER TIPS

It seems that you're going about this a bit wrong—you shouldn't be using branches for this purpose; instead, you should be using 2 separate repositories, a library one and an implementation/client one. The client one would then have the library repo as e.g. its submodule (although there are other approaches).

This way there wouldn't be any overlap in the files between those 2 repos, which is what you want, deciding by your requirements.

In order to convert what is currently in the form of 2 branches, you'd use git filter-branch to filter out from branch A files belonging to branch B, and vice versa, so you'd end up with 2 repos only having files of either the library or the client (implementation). This of course has the downside of modifying history, so if you want to avoid that, you'd just do the removing using normal git rm and git commit.

Avoid the deletion by cherry-pick-ing around it:

Yes, you can do this quite simply: you must avoid merging those commits which delete the files. So, of course, it will behoove you not to make commits which make changes and delete files.

Example:

Create a git repo with a master branch which has two files:

~$ mkdir gittest
~$ cd gittest
~/gittest$ git init
Initialized empty Git repository in /home/kaz/gittest/.git/
~/gittest$ echo foo > foo
~/gittest$ echo bar > bar
~/gittest$ git add foo bar
~/gittest$ git commit -m "root commit"
[master (root-commit) 1c1860f] root commit
 2 files changed, 2 insertions(+)
 create mode 100644 bar
 create mode 100644 foo

Shoot a topic branch from the root commit:

~/gittest$ git branch topic

Create a second version on master of both files:

~/gittest$ echo foo >> foo
~/gittest$ echo bar >> bar
~/gittest$ git diff
diff --git a/bar b/bar
index 5716ca5..a486f1a 100644
--- a/bar
+++ b/bar
@@ -1 +1,2 @@
 bar
+bar
diff --git a/foo b/foo
index 257cc56..0d55bed 100644
--- a/foo
+++ b/foo
@@ -1 +1,2 @@
 foo
+foo
~/gittest$ git commit -a -m "hack 1"
[master ded65d8] hack 1
 2 files changed, 2 insertions(+)

Switch to topic. Delete bar and commit. Modify foo in conflicting way with master and commit:

~/gittest$ git checkout topic
Switched to branch 'topic'
~/gittest$ git rm bar
rm 'bar'
~/gittest$ git commit -m "delete bar"
[topic 8a4009d] delete bar
 1 file changed, 1 deletion(-)
 delete mode 100644 bar
~/gittest$ echo "foo2" > foo
~/gittest$ git commit -a -m "replace foo"
[topic abfa329] replace foo
 1 file changed, 1 insertion(+), 1 deletion(-)

Recap history on topic:

~/gittest$ git log --oneline
abfa329 replace foo
8a4009d delete bar
1c1860f root commit

Back to master:

~/gittest$ git checkout master
Switched to branch 'master'
~/gittest$ git log --oneline
ded65d8 hack 1
1c1860f root commit

Now cherry pick abfa329 replace foo from topic, avoiding 8a4009d delete bar.

$ git cherry-pick abfa329
error: could not apply abfa329... replace foo
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

Fix the conflict on foo and commit:

~/gittest$ cat foo
<<<<<<< HEAD
foo
foo
=======
foo2
>>>>>>> abfa329... replace foo
~/gittest$ cat > foo
foo
foo2
~/gittest$ git add foo
~/gittest$ git commit -m "merged topic, avoiding deletion"
[master 320818f] merged topic, avoiding deletion
 1 file changed, 1 insertion(+), 1 deletion(-)

Note that bar is still there:

~/gittest$ ls bar
bar
~/gittest$ cat bar
bar
bar

Use git reset to undo unwanted deletions, during or after merge:

If you do pick up an unwanted deletion, you can undo it easily.

Let's roll back our example master branch to the hack 1 commit and rewrite that so that bar is not modified on master, only foo:

~/gittest$ git reset --hard ded65d8
HEAD is now at ded65d8 hack 1
~/gittest$ git reset HEAD^ -- bar
Unstaged changes after reset:
M   bar
~/gittest$ git commit --amend -m "hack1: bar only"
[master 623908a] hack1: bar only
 1 file changed, 1 insertion(+)
~/gittest$ git log --oneline
623908a hack1: bar only
1c1860f root commit
~/gittest$ git show -p
commit 623908a3bf6b2fb81b0bf8309fa2e83cd602986a
Author: Kaz <kaz@stackexchange.example.com>
Date:   Sat Dec 20 08:03:27 2014 -0800

    hack1: bar only

diff --git a/foo b/foo
index 257cc56..0d55bed 100644
--- a/foo
+++ b/foo
@@ -1 +1,2 @@
 foo
+foo

Now, let us merge topic without suspecting there is an unwanted deletion:

~/gittest$ git merge topic
error: Your local changes to the following files would be overwritten by merge:
    bar
Please, commit your changes or stash them before you can merge.
Aborting

Oops! I had forgot to git reset --hard after undoing the bar changes. Take two:

~/gittest$ git reset --hard
HEAD is now at 623908a hack1: bar only
~/gittest$ git merge topic
Auto-merging foo
CONFLICT (content): Merge conflict in foo
Removing bar
Automatic merge failed; fix conflicts and then commit the result.

Okay, so the merge automatically deleted bar (no conflict there because bar is not modified on master). There is a conflict on foo, exactly the same as in the cherry-pick case, so let's deal with that first:

~/gittest$ cat > foo
foo
foo2
~/gittest$ git add foo

Now, roll back the deletion of bar. Note that bar is not in the working tree and remains that way for now:

~/gittest$ git reset HEAD^ -- bar
Unstaged changes after reset:
D   bar

We commit everything:

~/gittest$ git commit -m "merged topic, except deletion of bar"
[master 57b7fca] merged topic, except deletion of bar

Now we bring back bar:

~/gittest$ git reset
Unstaged changes after reset:
D   bar

Oops, I meant --hard:

~/gittest$ git reset --hard
HEAD is now at 57b7fca merged topic, except deletion of bar

Review the log, with --graph:

~/gittest$ git log --graph --oneline
*   57b7fca merged topic, except deletion of bar
|\  
| * abfa329 replace foo
| * 8a4009d delete bar
* | 623908a hack1: bar only
|/  
* 1c1860f root commit

Verify file contents. foo is merged, bar is there:

~/gittest$ cat foo
foo
foo2
~/gittest$ cat bar
bar
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top