问题
Sorry that my question is so vague, but I don't really know how to describe the situation best.
In my repository the README
file contains a link, which has the branch name master
or develop
in it for the respective branches (only those two branches are affected, I don't really care how it is handled for different branches). What I want now, is that if I merge develop
into master
this difference is preserved.
What I did up to now, was using
git merge --no-commit develop
And manually fix the link. The problem here is, that develop
has then an independent history of master
. Thus, git describe
from the develop
branch doesn't include the latest tag on master. I use this however for versioning, so it is essential to have this. A work around is to then merge master
back into develop
git merge --no-commit master
This however causes a messy history, as I have plenty of these empty back-merges.
I also tried this, where I add README.rst merge=ours
. However, this ignores all changes to README
thus I would have to manually update the README
file.
I don't really know if there is a suitable approach. Is there a way I can tell GitHub to ignore the links (e.g. via a regular expression)? Or maybe some command that expands into the branch name?
回答1:
You can't (quite) get what you want. You might be able to get what you need, depending on where you need it. Consider using a smudge filter to alter the file as it gets checked out. You need a corresponding clean filter to remove the smudging, so that what's in the actual commits is un-smudged.
For more about setting up smudge and clean filters, see the gitattributes documentation.
Background
First, remember that a branch name is just a moveable pointer to a (single) commit. There is something else that people also refer to using the word branch, which is: a series of commits ended by a particular commit. For more about this, see What exactly do we mean by "branch"?
In general, what Git does is add commits. You check out some branch name:
$ git checkout master
and then do some work (on files in your work-tree, which you then copy into the staging area aka index using git add
to overwrite the copies that were already in the index but match the current commit—we'll come back to all this in a bit):
... edit ...
$ git add README.rst
Eventually, you run git commit
, which:
- collects a log message from you;
- packages up all the files that are in the index right now; and
- uses those (plus your name and email and the time) to make a new commit.
The new commit's parent commit is the commit you checked out, when you ran git checkout master
. The new commit becomes the one at the tip of the branch you chose, when you ran git checkout master
. So the name master
now points to the new commit, which points back to what was the master
commit just a moment ago:
... <-previous-tip-of-master <-new-tip-of-master <--master
You can only check out one branch at a time, and git commit
makes its new commit on that branch—specifically, by making that name point to the new commit Git just made. All other branch names are unchanged: they still point to some other commit(s).
Using git merge
still just makes a new commit! The difference between this new commit and other new commits made by git commit
is that this new commit has two parents: it points back to the previous commit as usual, but it also points back to the commit you chose when you ran git merge
. In any case this new commit is only on the branch you're on:
...--B--...--o--C--M <-- master
\ /
o--...-o--D <-- develop
where M
is the new merge commit. This new merge commit has a snapshot of all files, just like every other commit has a snapshot of all files, so the README.rst
file in M
is built by doing something(s) with the snapshot in C
—which was the tip of master
a moment ago—and something(s) with the snapshot in D
, which is still the tip of develop
. These "somethings" are based on how C
and D
have changed since the merge base commit version of README.rst
that's in the snapshot in commit B
.
Making use of this: smudge and clean filters
Now, along with this metadata about who made it, when, and with what log message, each commit simply records all the source files that were in the index / staging-area at the time you ran git commit
. What you get when you git checkout <thing-that-specifies-commit>
is derived from this snapshot, but is not necessarily the same as this snapshot.
All of these details are important! Git will extract the commit in M
into the index / staging-area. What's in this index / staging-area at this point exactly matches whatever is inside M
. But this isn't what you see in your work-tree. What you see in your work-tree is obtained by running whatever Git copied into the index through any smudge filter you define.
The default smudge filter is basically "leave everything unchanged". You can get end-of-line adjustments to happen at this point. Technically this is separate from smudge filters, but it's all part of this smudging process. If you use this default, what's in your work-tree matches what's in your index.
Suppose, though, that you define a smudge filter to apply specifically to README.rst
. Suppose further that you write this smudge filter to find the line with the link that is supposed to mention the branch, master
or develop
. Write your smudge filter to replace this line with the correct link. For instance, perhaps the line you commit should read:
blah blah link:!replace this part! blah
and your smudge filter says:
sed -e "s/!replace this part!/actual link using $branch/"
(using the Linux/Unix/GNU sed
command, but you can write this however you like). Make your smudge filter code figure out what branch name to use by, e.g., running git rev-parse --abbrev-ref HEAD
.
This means that what's in the work-tree for file README.rst
will no longer match what's actually committed.
To make this work properly, though, you need to make git add
write, as the thing-to-be-committed, a line that reads:
blah blah link:!replace this part! blah
To do this automatically, write yourself a clean filter that finds the smudged line—however you choose to do that—and replaces it, e.g., using sed
, with the !replace this part!
text.
Now when you run:
$ git add README.rst
Git will run the work-tree file through your clean filter to get the version of README.rst
to write into the index / staging-area.
This also works when merging: the merge will "see" the cleaned version of the file, and merge any changes you make, on either branch, using the cleaned text. The "dirty" (smudged) version that has the actual link appears only in the work-tree. It never goes into any commit.
Whether this does what you need depends on what reads the link. If whatever reads the link, does so by extracting commits to a work-tree, and uses the smudge filter, than that thing reading the file will read the dirtied-up actual link. If the thing reading the file somehow bypasses all of this and gets the raw committed content, it will see the "cleaned" link.
If you store the cleaned link as a version suitable for most uses, and only need the dirty version that points to develop
instead of master
when used out of the work-tree, this will work even for that case as well.
来源:https://stackoverflow.com/questions/49624988/branch-specific-lines-in-git