I want to share recipes for solving a couple of tasks that sometimes arise when working with git, and which are not "directly obvious."
At first I thought to accumulate more similar recipes, but there is a time for everything. I think if there is a benefit, then it is possible and gradually ...
So...
Preamble. There is a main branch ( master
), in which new features and fixes are actively committed; there is a parallel branch feature
, in which the developers swam for some time into their own nirvana, and then suddenly discovered that they hadn’t been merged with the master for a month, and the merge head-on (head with head) had already become nontrivial.
(Yes, this is not about an ideal world, where everything is correct, there is no crime, children are always obedient and even cross the road strictly by the hand with the mother, carefully looking around).
Purpose: to bear. In this case, that it was a "clean" merg, without features. Those. so that in a public repository in the graph of branches, two threads are connected at a single point with the message "merged branch 'master' into feature". And the whole “this one here” headache about how much time and effort it took, how many conflicts it was decided and how much hair it turned gray has not been kept.
The plot. The fact that in the gita you can edit the last commit with the key - --amend
knows everything. The point is that this "last commit" can be anywhere and contain anything. For example, it may not be just the “last commit to a linear branch”, where they forgot to correct a typo, but also a merge commit from a normal or “octopus” merge. --amend
will roll out the proposed changes exactly the same way and "inline" the changed commit into the tree, as if it really appeared as a result of honest merging and conflict resolution. In essence, git merge
and git commit --amend
allows git commit --amend
to completely separate the "sticking of a place" ("this commit will be HERE in the tree") and the contents of the commit itself.
The basic idea of ​​a complex merge commit with a clean history is simple: first, "we collapse the place", creating a clean merge commit (regardless of the contents), then rewrite it with - --amend
, making the contents "correct."
"Pounding place". This is easy to do by assigning a strategy during the merzh, which will not ask unnecessary questions about conflict resolution.
git checkout feature git merge master -s ours
Oh yes. It was necessary to create a "backup" branch from the head feature before merge. After all, nothing is really merged ... But let it be the 2nd point, not 0th. In general, we are switching to a non-non-merged feature, and now we honestly merge. Any affordable way, regardless of any "dirty khaki." My personal way is to look through the master branch from the moment of the last merge and evaluate possible problem commits (for example: they corrected a typo in one place - not a problem. Massively (into many files) they renamed some essence - a problem, etc.). From problematic commits we create new branches (I do unsophisticated - master1, master2, master3, etc.). And then we merge a branch for a branch, moving from old to fresh and correcting conflicts (which are usually self-evident with this approach). Offer other methods (I'm not a magician; I'm just learning; I will be glad to constructive comments!). In the end, having spent (maybe) several hours on purely routine operations (which can be trusted to a junior, because there are simply no complicated conflicts with this approach), we get the final code state: all innovations / fixes of the wizard are successfully ported to the feature branch, all relevant tests this code went through, etc. Successful code must be commited.
Rewriting the "success story". Being on commit, where "everything is done", run the following:
git tag mp git checkout mp git reset feature git checkout feature git tag -d mp
(I decipher: with the help of the tag (mp - merge point) we go to the detached HEAD state, from there reset to the head of our branch, where at the very beginning there is a "fixed place" with a fraudulent merge commit. The tag is no longer needed, therefore we delete it). Now we are standing on the original "clean" merge commit; at the same time in the working copy we have the "correct" files, where everything we need is correct. Now you need to add all the modified files to the index, and especially carefully look at non-staged (there will be all the new files that have arisen in the main branch). All the necessary from there we add too.
Finally, when everything is ready, we enter our correct commit into the reserved place:
git commit --amend
Hooray! Everything worked out! You can casually push a branch into a public repository, and no one will know that you actually spent half a day of working time on this merge.
Preamble: in the file the code-code, and finally nakodili so that even the visual studio began to slow down, digesting it (not to mention JetBrains). (Yes, we are again in the "imperfect" world. As always).
Smart brains thought and thought, and identified several entities that can be budded into a separate file. But! If you just take a copy of a piece of file and paste it into another file, this will be a completely new file from the git point of view. In case of any problems, a search through the history will unequivocally indicate only "where is this invalid?" Who divided the file. And to find the original source is sometimes not necessary “for repression”, but purely constructive - to find out WHY this line was changed; what bug it fixed (or fixed any). I want the file to be new, but the whole history of changes still remains!
The plot. With some slightly annoying edge effects, this can be done. For definiteness, there is a file file.txt
from which you want to select a part in file2.txt
. (and at the same time save the story, yes). Run this snippet:
f=file.txt; f1=file1.txt; f2=file2.txt cp $f $f2 git add $f2 git mv $f $f1 git commit -m"split $f step 1, converted to $f1 and $f2"
As a result, we obtain the files file1.txt
and file2.txt
. They both have exactly the same story (real; like the source file). Yes, the original file.txt
had to be renamed; this is the "slightly annoying" edge effect. Unfortunately, I could not find a way to save the story, but in order not to rename the source file, I could not (if anyone could, tell me!). However, git can handle it all; no one bothers to rename the file back with a separate commit:
git mv $f1 $f git commit -m"split finish, rename $f1 to $f"
Now at file2.txt
gilt will show the same line history as the original file. The main thing is not to merge these two commits together (otherwise all the magic will disappear; I tried!). But at the same time, no one bothers to edit files directly in the process of separation; it is not necessary to do this later with separate commits. And yes, you can select many files at once!
The key point of the recipe: rename the source file to another in the same commit where a copy is made from it (and, possibly, edited). And let this commit live in the future (never get rid of a reverse renaming).
Upd: a couple of recipes from Lissov
You are on the latest version of the initial repository. The task is to separate one folder. (I’ve seen options for several folders, but it’s simpler and clearer to either put everything in one at first, or repeat what’s written below several times.)
Important! All movements are done with the git mv
, otherwise git could lose the story.
We carry out:
git filter-branch --prune-empty --subdirectory-filter "{directory}" [branch]
{directory} is the folder to be separated. As a result, we get a folder along with the full history of commits only into it, that is, in each commit, only files from this folder are displayed. Naturally, part of the commits will be empty, removed by --prune-empty.
Now we change origin:
git remote set-url origin {another_repository_url}` git checkout move_from_Repo_1
If the second repository is clean, you can immediately in master. Well, push:
git push -u move_from_Repo_1
The entire snippet (for easy copy-paste):
directory="directory_to_extract"; newurl="another_repository_url" git filter-branch --prune-empty --subdirectory-filter "$directory" git remote set-url origin "$newurl" git checkout move_from_Repo_1 git push -u move_from_Repo_1
Suppose you have done what is above 2 times and got the move_from_Repo_1
and move_from_Repo_2
, and in each one you transferred files using git mv
to where they should be after the merge. Now it is necessary to bear:
br1="move_from_Repo_1"; br2="move_from_Repo_2" git checkout master git merge origin/$br1 --allow-unrelated-histories git merge origin/$br2 --allow-unrelated-histories git push
The focus is on "--allow-unrelated-histories". As a result, we get one repository with a complete history of all changes.
Source: https://habr.com/ru/post/424045/