1. 程式人生 > >How to make Git preserve specific files while merging

How to make Git preserve specific files while merging

How to make Git preserve specific files while merging

Oh boy, are branches great. They let you have entirely different versions of a given file, depending on the context.

The thing is, in a few (not so rare) situations, you may want to version a file that changes from branch to branch, but retain its current content when merging another branch into yours

.

The usual suspects are non-sensitive files that vary based on the runtime context (development, staging, production) because they contain URLs, domain names or port numbers that need to adjust:

  • e-mail server configuration that would use a local handling outside of production (e.g. through the excellent
    mailcatcher
    )
  • log configuration that would dump to local-disk files in dev, but consolidate to some central service otherwise
  • etc.

This kind of file sure needs versioning. But when you merge another branch into yours (say you’re doing a control merge of master), how do you retain your current version for just these files, without having to resort to special commands or custom workflows?

That’s easy, actually. Let me show you how.

To retain our current version of a file during a merge (a merge is always incoming, remember: we merge into the current branch), we need to make use of an oft-ignored Git feature: Git attributes.

Git attributes

This mechanism lets us map files or folders (we use globbing patterns such as secure/* or *.svg) to specific technical properties.

These mappings are usually versioned themselves, just like what we would put in .gitignore files, but these are stored in .gitattributes (and just like .gitignore has a strictly-local buddy at .git/info/exclude, we also have .git/info/attributes).

The format is simple: every line that neither is empty nor starts with a hash (#) sign to denote a comment uses a globbing-pattern = attribute-info format (the amount of whitespace being irrelevant).

An attribute can be set (present with no specific value), unset (present in negative form), set to a value or unspecified. For our purpose here, we’ll use a specific value.

While this lets us create custom attributes, or group together attribute combos as meta-attributes, Git does come with a fair number of predefined attributes that let you do amazing things

Merge drivers

What we’re interested in here is the merge attribute, that lets us map files to a merge driver, a command responsible for the actual merging of these files.

This attribute has default values based on the detected type for this file: it would normally be considered text or binary.

We can, however, create our own merge drivers (and define these in our usual Git configuration, say our ~/.gitconfig file), then use attributes to map specific files to our drivers. Git can call such a driver with up to three arguments, in whatever order we specify: paths to the common-ancestor (merge base, in Git parlance) version of the file, to our version, and to the merged branch’s version.

The key point is that such a pilot is supposed to store the result of the merge in our own file if it manages the merge properly, which it indicates by exiting with a zero exit code (as per POSIX usual). So, a driver that does not touch the files and exits with code zero leaves our current file alone during a merge.

Eureka!

We don’t even need to write an empty script (or one that would just exit 0), because in any Bash/zsh/shell environment you’ll find a true command, often a shell built-in, that does just that. Let’s use that.

Setting up

So let’s start by defining a merge driver that would always favor our current version of the file, by making use of the existing true command. We’ll call this driver ours, to keep in line with similar merge strategies:

git config --global merge.ours.driver true

Do you already have a Git repo for testing? Oooh, let’s smudge it! Or, let’s just whip a repo up:

mkdir tmp
cd tmp
git init
git commit --allow-empty -m "chore: Initial commit"

Now let’s add a .gitattributes file at the root level of our repo, that would tell email.json to use that driver instead of the standard one:

echo 'email.json merge=ours' >> .gitattributes
git add .gitattributes
git commit -m 'chore: Preserve email.json during merges'

There, we’re good to go!

Prepping for a test run

Let’s just put ourselves in a relevant test situation, first with a file that will start as common before branching out:

echo 'Oh yeah' > demo-shared
git add demo-shared
git commit -m 'chore(demo): a file that will merge normally'

Then let’s make a demo-prod branch and put some mixed work in there:

git checkout -b demo-prod
echo '{"server":"smtp.mandrillapp.com","port":587}' > email.json
git add email.json
git commit -m 'chore(email): production email.json'
echo -e "You know what?\nOh yeah" > demo-shared
git commit -am 'fix(demo): Header for the normal-merge file'

Finally, let’s go back to our previous branch and add some mixed work in it too:

git checkout -
echo '{"server":"localhost","port":1025}' > email.json
git add email.json
git commit -m 'chore(email): dev/staging email.json'

echo -e 'You betcha' >> demo-shared
git commit -am 'fix(demo): Footer for the normal-merge file'

Alright, go!

OK, we’re all set to test this baby. If we attempt to merge our current branch in demo-prod, the demo-shared file should merge normally (without conflicts, too), but we should retain our production variant of email.json:

(master) $ git checkout demo-prod
(demo-prod) $ git merge -
Auto-merging demo-shared
Merge made by the 'recursive' strategy.
demo-shared | 1 +
1 file changed, 1 insertion(+)

(demo-prod) $ cat email.json
{"server":"smtp.mandrillapp.com","port":587}

Victory! \o/

I’d like to thank Scott Chacon who, in the chapter about attributes of his Pro Git book, put this tip forth; also, Julien Hedoux who, by just asking me how this could be done, had me delve into the issue and dig this up.

Want to learn more?

I wrote a number of Git articles, and you might be particularly interested in the following ones:

Also, if you enjoyed this post, say so: upvote it on HN! Thanks a bunch!

Although we don’t publicize it much for now, we do offer English-language Git training across Europe, based on our battle-tested, celebrated Total Git training course. If you fancy one, just let us know!

(We can absolutely come over to US/Canada or anywhere else in the world, but considering you’ll incur our travelling costs, despite us being super-reasonably priced, it’s likely you’ll find a more cost-effective deal using a closer provider, be it GitHub or someone else. Still, if you want us, follow the link above and let’s talk!)

相關推薦

How to make Git preserve specific files while merging

How to make Git preserve specific files while mergingOh boy, are branches great. They let you have entirely different versions of a given file, depending o

How to Correctly Store App-Specific Files in Android

Christophe Versieux (Waza_be) posted a rant about android developers' bad habit to store files directly on the root o

How to make a GroupBox in website development by VS.NET2005

Sometimes we need to make a GroupBox on my webpage.Using the HTML object(fieldset ,legend)  we can make it out! source: <fieldset style

【 InkGenius】Good developers who are familiar with the entire stack know how to make life easier for those around

Good developers who are familiar with the entire stack know how to make life easier for those around

How to make your iOS apps more secure with SSL pinning

swift 和 obj-c 完成 ssl 的寫法如下: We can start by instantiating an NSURLSession object with the default session configuration. Swift self.urlSession = NSURLSes

[iOS] How to make a Global function in Swift

You can create a custom class with the method you need like this: class MyScene: SKScene { func CheckMusicMute() { if InGameMusicOnOff == tr

How to make HTTP Post request with JSON body in Swift

Try this, // prepare json data let json: [String: Any] = ["title": "ABC", "dict": ["1":"First", "2":"Second"]] let jsonDat

How to Make a Profit in a Bear Market?

How to Make a Profit in a Bear Market?2018 has been a tough year for crypto investors. Bitcoin has declined by more than 60% and leading altcoins have lost

How to Make Sure You Can Trust Your Artificial Intelligence

We know artificial intelligence will remake -- is already in the process of remaking -- both business and the broader world beyond. What we don't know yet

How to make the impossible possible in CSS with a little creativity

CSS Previous sibling selectors don’t exist, but that doesn’t mean we can’t use themIf you ever used CSS sibling selectors, you know there’s only two. The +

How to make money writing Blogs

If the product is free, you are the productOur story and the problems with new mediaIntroductionThis article is free. You can read it at any time from any

Startup idea: Basic Income, how to make this happen?

The idea of an universal basic income is surfacing here and there. Having lost almost half my life (I'm 40) to useless grind work, I'm determined to change

How to make form submissions secure on an API website

Implementing forms on a Vue.js website? Having a readonly website is a piece of cake. Easy to develop using headless CMS, easy to maintain, and zero worrie

Help! My company doesn't know how to use Git for production ready releases

My company is still widely using CVCS (Central Version Control Systems) tools mostly SVN. We've just now been slowly integrating Git into our department.Ou

How to Make Linear Gradient View

React Native lets us build mobile apps using only Javascript. It works by providing a common interface that talks to native iOS and Android components. The

How to make onActivityResult get called on Nested Fragment

One of the common problem we always meet in the world of Fragment is: although we could call startActivityForResult directly from Nested

Listeners with several functions in Kotlin. How to make them shine?

One question I get often is how to simplify the interaction with listeners that have several functions on Kotlin. For listeners (or any interfaces) w

How to use Git in a secure way

How to use Git in a secure wayWe live in a world where it is hard not to know Git, the most popular Distributed Version Control System (DVCS). Free and ope

How to Make Your Code Readable

How Do You Identify Bad Code?The simplest way to identify bad code, in my opinion, is to try to read your code as if it were a sentence or phrase.Here, for

How to make your own Python dev

In simple terms, Raspberry Pi is a super cheap ($40) Linux based computer. That’s it. Seriously.It can do whatever you can imagine a normal Linux computer