190

I have a repo called myrepo on the remote beanstalk server.

I cloned it to my local machine. Created two additional branches: staging and dev. Pushed these branches to remote as well.

Now:

 local                   remote                   server
 --------------------------------------------------------  
 master  ==> Pushes to  `master`  ==> deployed to `prod`
 staging ==> Pushes to  `staging` ==> deployed to `staging`
 dev     ==> Pushes to  `dev`     ==> deployed to `dev`

I have a file called config.xml which is different on each branch.

I want to ignore this file only during merges. But I want this to be included when I checkout or commit from/to the repo branch.

The reason I want this is, we have a deploy script that pulls (checkout) the specific branch and deploys on the respective servers. So we need config.xml file of that specific branch go into the specific server as indicated above when deployed.

I guess .gitignore wont work. What are the other options? Note that the ignored file should be part of checkout and commit, which is important. it should be ignored only during merges.

Thanks!

George WS
  • 3,903
  • 5
  • 27
  • 39
Kevin Rave
  • 13,876
  • 35
  • 109
  • 173
  • In its default mode, git pull is shorthand for git fetch followed by git merge FETCH_HEAD. So you statements kind of conflict with each other. – zzk Mar 05 '13 at 19:07
  • Well, I would say, its checkout. Not pull. I will update the question to be clear. – Kevin Rave Mar 05 '13 at 19:08
  • 1
    possible duplicate of [Git: ignore some files during a merge (keep some files restricted to one branch)](http://stackoverflow.com/questions/3868491/git-ignore-some-files-during-a-merge-keep-some-files-restricted-to-one-branch) – givanse Feb 12 '14 at 21:24
  • 3
    did you ever find a solution to this? git attributes are only useful for the case where the file has conflicts between the branches being merged, so its not always enough. – pvinis Aug 23 '16 at 06:00
  • did you look into symbolic (not followed by git) or even hard links to the rescue? – Frank N Nov 01 '17 at 08:10

9 Answers9

173

I got over this issue by using git merge command with the --no-commit option and then explicitly removed the staged file and ignore the changes to the file. E.g.: say I want to ignore any changes to myfile.txt I proceed as follows:

git merge --no-ff --no-commit <merge-branch>
git reset HEAD myfile.txt
git checkout -- myfile.txt
git commit -m "merged <merge-branch>"

You can put statements 2 & 3 in a for loop, if you have a list of files to skip.

unmesh-gurjar
  • 1,739
  • 2
  • 10
  • 3
73

I ended up finding git attributes. Trying it. Working so far. Did not check all scenarios yet. But it should be the solution.

Merge Strategies - Git attributes

Brett
  • 19,449
  • 54
  • 157
  • 290
Kevin Rave
  • 13,876
  • 35
  • 109
  • 173
  • 4
    but I found this [git-scm.com/book/ch7-2.html#Merge-Strategies](http://git-scm.com/book/ch7-2.html#Merge-Strategies) – Nate- Aug 16 '13 at 17:28
  • 1
    Here is the correct link for the merge strategies section of the git attributes docs: https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes#_merge_strategies – Adrian Laurenzi Jan 12 '18 at 19:27
  • 3
    Found this well explained in an article https://medium.com/@porteneuve/how-to-make-git-preserve-specific-files-while-merging-18c92343826b – ShawnFeatherly Mar 01 '18 at 20:00
  • 26
    This seems to be a problem if there are no conflicts? Files will still be merged? – Brett Jul 21 '18 at 10:10
  • @Brett, In fact, you can keep this file always conflicting on every branch, so you can avoid automatic merging because of there is no conflict. For example, add one line to the file of every branch: `{branch_name}` – liber Jan 19 '22 at 02:56
34

.gitattributes - is a root-level file of your repository that defines the attributes for a subdirectory or subset of files.

You can specify the attribute to tell Git to use different merge strategies for a specific file. Here, we want to preserve the existing config.xml for our branch. We need to set the merge=foo to config.xml in .gitattributes file.

merge=foo tell git to use our(current branch) file, if a merge conflict occurs.

  1. Add a .gitattributes file at the root level of the repository

  2. You can set up an attribute for confix.xml in the .gitattributes file

     <pattern> merge=foo
    

    Let's take an example for config.xml

     config.xml merge=foo
    
  3. And then define a dummy foo merge strategy with:

     $ git config --global merge.foo.driver true
    

If you merge the stag form dev branch, instead of having the merge conflicts with the config.xml file, the stag branch's config.xml preserves at whatever version you originally had.

for more reference: merge_strategies

eigenharsha
  • 2,051
  • 1
  • 23
  • 32
  • 32
    But what if there is no conflict? How to always keep a certain file during merge? – Igor Yalovoy Apr 16 '18 at 13:53
  • 2
    @IgorYalovoy: If there is no conflict ... well, this is a little tricky as it actually works from hash IDs, but: if `config.xml` is *completely unchanged* from the merge base to either branch tip version, Git just takes the changed version, without looking at the `merge.ours.driver` setting. So you are correct to be concerned. – torek Jun 26 '18 at 12:11
  • 1
    copy-paste from Git Docs: Merge Strategies - Git attributes. [Link](https://stackoverflow.com/a/15233458/3743145) – kyb Sep 07 '18 at 19:02
  • This method results in "merge commits" every time FYI. – rsmets Feb 15 '19 at 20:43
  • 3
    "ours" appears to be an arbitrary name for the new strategy being introduced. I found this confusing, because `theirs` is a built-in merge strategy. But it seems that one can write ` merge=foo`, then `git config --global merge.foo.driver true`, and it will work the same way. – Kyle Strand Oct 14 '19 at 18:22
  • 1
    Adding a `.gitattributes` file to the `main` branch with ` merge=ours`, then running `git config --global merge.ours.driver true` worked great. The config file in `dev` was not merged into the config file in `main`. Unfortunately, during the merge the `.gitattributes` file was deleted (because it did not exist in `dev`)! How do avoid this? Just add the same `.gitattributes` file to `dev`? Would the `ours` value cause a conflict? – ericOnline May 06 '21 at 19:02
  • Ended up: Created `.gitattributes` in `dev` branch according to above comment, add, commit, push to `dev`. Checkout `main`, `git merge dev`, push to `main`. This successfully ignored the file in question. – ericOnline May 06 '21 at 19:09
11

Example:

  1. You have two branches: master, develop
  2. You created file in develop branch and want to ignore it while merging

Code:

git config --global merge.ours.driver true
git checkout master
echo "path/file_to_ignore merge=ours" >> .gitattributes
git merge develop

You can also ignore files with same extension

for example all files with .txt extension:

echo "*.txt merge=ours" >> .gitattributes
Sole Sensei
  • 369
  • 3
  • 8
9

You could start by using git merge --no-commit, and then edit the merge however you like i.e. by unstaging config.xml or any other file, then commit. I suspect you'd want to automate it further after that using hooks, but I think it'd be worth going through manually at least once.

gcbenison
  • 11,723
  • 4
  • 44
  • 82
  • 2
    Does `git attributes` offer any straight solution? I am not able to understand this. http://git-scm.com/book/en/Customizing-Git-Git-Attributes#Merge-Strategies – Kevin Rave Mar 05 '13 at 19:24
7

As commented by many it should be noted that the accepted answer and the copies of it (using a custom merge strategy for a specific file or files) only works in cases where there would be actual merge conflicts.

2 simple cases that would not result in merge conflicts and therefore commits getting merged:

  • You have a config.xml file in a feature branch and not in master. You want to exclude it during merge
  • Your config.xml file in both branches are based off the same commit. New code is added to the file in feature.

Either use merge --no-commit as demonstrated by unmesh-gurjar or simply cherry-pick invidiual commits into master. The latter being rather cumbersome of course and coming with a couple of other pitfalls (new SHA1 IDs etc.)

MattSchmatt
  • 850
  • 8
  • 18
  • 2
    What is the resolution to this? – Gabriel Jun 20 '21 at 13:00
  • @Gabriel: The sad truth is there is no set-and-forget solution as simple as adding a file to a .gitignore afaia. You'd need to use one of the workarounds above (which they are imho) or unfortunately work with other VCSs. – MattSchmatt Nov 01 '21 at 13:07
5

You could use .gitignore to keep the config.xml out of the repository, and then use a post commit hook to upload the appropriate config.xml file to the server.

tlehman
  • 5,125
  • 2
  • 33
  • 51
  • 2
    I know thats kind of hack. I am trying to find a clean solution. What about git `attributes`. Any idea how it works? I am not able to understand this. http://git-scm.com/book/en/Customizing-Git-Git-Attributes#Merge-Strategies – Kevin Rave Mar 05 '13 at 19:26
  • 1
    I have not heard of these, I'm trying to apply the merge=ours attribute, looks promising. – tlehman Mar 05 '13 at 19:32
  • 1
    Git attributes are awesome! You can even tell git how to diff non-text files, so you could use pdf2text to diff PDFs! – tlehman Mar 05 '13 at 19:39
3

Here git-update-index - Register file contents in the working tree to the index.

git update-index --assume-unchanged <PATH_OF_THE_FILE>

Example:-

git update-index --assume-unchanged somelocation/pom.xml

Sireesh Yarlagadda
  • 12,978
  • 3
  • 74
  • 76
1

Consider using git hooks:

After you run a successful git checkout, the post-checkout hook runs; you can use it to set up your working directory properly for your project environment. This may mean moving in large binary files that you don’t want source controlled, auto-generating documentation, or something along those lines.

Source: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#:~:text=After%20you%20run,along%20those%20lines.

You can put these files in a separate directory that if necessary, can be chosen to exclude from the repository through .gitignore.

Based on the name of the branch that is being switched to, the script from the post-checkout hook can then take the correct files (could be in subdirs) for that branch and copy (overwriting) them to the correct locations. These individual files that are branch-specific, need to be in .gitignore.

Example of post-checkout script in .git/hooks:

#!/bin/sh

BRANCH=`git reflog | awk 'NR==1{ print $8; exit }'`

cp "./configs/$BRANCH/config.json" "./config.json"

exit 0

Pieter
  • 1,751
  • 3
  • 30
  • 65