I have following git branches.
B1 B2
From the B1, I created the branch S1 and started working on it and a bunch of changes are committed. At the later point, I merged the changes from B2 to S1. After this also, lot of commits are added in S1.
Now I want to push the changes to remote S1. There are lot of unwanted commits in between. I don't want these histories to be pushed to remote. Is there any way to push my changes to remote S1 without log.
CodePudding user response:
Yes. From branch S1
, you do a git rebase -i B1
. It will open your editor.
Then you keep the bottom-most commit as pick
, all the others you replace pick
with squash
(or fixup
if you don't want to keep the git message).
Save and close. Git will "squash" the commits into a single commit.
Notice that if you're using GitHub or GitLab, there are repository options to always squash pull/merge requests into a single commit.
CodePudding user response:
Let me start with some gentle (I hope) corrections to how you're thinking about Git in general.
Now I want to push the changes to remote S1.
Git is, fundamentally, about commits. You literally do not push changes, you push commits. Each commit holds a snapshot and metadata. We "see" changes by comparing two selected commits: whatever is different in their two snapshots, that's what we "see" as a "change". The commits don't contain these changes though! They just contain the snapshots in question.
There are lot of unwanted commits in between. I don't want these histories to be pushed to remote.
What this means is that you have some commits whose final effects you like—the overall change made via those commits is good—but for some reason you don't like each of the individual commits.
You must therefore make new commits (one, two, or many such commits) where you like both the snapshot in each such commit and the effective difference.
YuriAlbuquerque's answer gives you two methods by which you can make new commits: using git rebase -i
, and using a "squash-and-merge" operation on GitHub or GitLab.
Note that you can also do a "squash-and-merge" directly in Git. Depending on the topology of the graph of commits you've made and the final topology you want, it's possible that you might not be able to do this as simply as using one git merge --squash
. But that's what I'll show below, after going through some caveats.
Caveat emptor
I have to make a lot of assumptions, given what you have said and what is missing (implied, but not said).
I think the process and the reasoning here is best shown by illustration, where we draw the initial and final commit results, as Code-Apprentice suggested in a comment. The thing to note here is that the graph is crucial. The graph is how Git finds the commits, and since Git is all about the commits, we have to be able find them. We want them to be, well, wherever we want them to be.
I have [branches branch1 and branch2]
Let's draw this, but again, note that I'm making assumptions that might be false:
C--D--E <-- branch1
/
...--o--B
\
F--G--H <-- branch2
From the [first branch], I created [a third branch branch3]
Let's draw that in, with the assumption that branch3
is now the current branch for making new commits:
C--D--E <-- branch1, branch3 (HEAD)
/
...--o--B
\
F--G--H <-- branch2
and started working on it and a bunch of changes are committed
We add new commits to branch3
like so. I'll just use two commits in the example; any number would suffice as long as it's more than one:
I--J <-- branch3 (HEAD)
/
C--D--E <-- branch1
/
...--o--B
\
F--G--H <-- branch2
At the later point,
Uh oh, now we're missing information. What other commits may have occurred on any of these three branches before we get to this "later point"?
I merged the changes from [
branch2
] to [branch3
]
Uh oh part 2: we're missing even more information. "Merged changes from branch2" by itself doesn't mean that much,1 but "ran git switch branch3; git merge branch2
" does, so let's assume that this is what you mean. We now go back to our drawing, making the bold assumption that no other commits have occurred while we went from "earlier point" to "later point":
I--J <-- branch3 (HEAD)
/
C--D--E <-- branch1
/
...--o--B
\
F--G--H <-- branch2
A git merge
operation here would locate the merge base of the two tip commits of interest, commits J
and H
. That merge base is commit B
. Git would then run two git diff
operations:
git diff --find-renames <hash-of-B> <hash-of-J> # what we changed
git diff --find-renames <hash-of-B> <hash-of-H> # what they changed
and attempt to combine these sets of changes and make a new merge commit. If all goes well, we get:
C--D--E <-- branch1
/ \
...--o--B I--J
\ \
\ M <-- branch3 (HEAD)
\ /
F---G----H <-- branch2
After this also, lot of commits are added in [
branch3
].
Let's draw "a lot of commits" (two more, N
and O
):
C--D--E <-- branch1
/ \
...--o--B I--J
\ \
\ M--N--O <-- branch3 (HEAD)
\ /
F---G----H <-- branch2
Now I want to push [some other commits] to [some remote, i.e., some other Git repository]
(aside: S1
shouldn't be both a remote, like origin
, and a branch name: that's much too confusing for us humans).
Is there any way to push [these commits] to [the other Git repository] without [these commits being the ones that show up in the other Git repository?]
No: if you push these commits, these commits are the commits you push!
You need to some some other commit or commits.
What other commits do you want? That depends on the graph you want. Note that the graph we have now joins commits H
and J
to make merge commit M
, and all of these commits—the entire separate C-D-E-I-J
and F-G-H
sequence plus the joining M
plus the N-O
—add on to commit B
.
We can very easily make one new commit S
(for "squash") that contains the same snapshot as commit O
, but simply adds on to commit B
:
S <-- branch4 (HEAD)
/
| C--D--E <-- branch1
|/ \
...--o--B I--J
\ \
\ M--N--O <-- branch3
\ /
F---G----H <-- branch2
If we now push branch4
's commit S
to the other Git repository, then—assuming they don't have commits C-D-E
and F-G-H
already!—they will have just these commits:
...--o--B--S <-- some-branch-name
(where we achieve this by running git push that-remote branch4:some-branch-name
after we make commit S
). The diff from B
to S
will be the same as the diff from B
to O
: that is, someone using S
as a single ordinary commit will "see" all of the changes that we made to get to commit O
.
But if they do have commits C-D-E
and F-G-H
already, presumably on branches branch1
and branch2
like this:
C--D--E <-- branch1
/
...--o--B
\
F--G--H <-- branch2
then new commit S
still just adds on to existing commit B
, giving htem:
C--D--E <-- branch1
/
...--o--B--S <-- some-branch-name
\
F--G--H <-- branch2
This means that the "changes" they see, comparing B
to S
, include the changes that they see when comparing B
to E
and the changes that they see when comparing B
to H
! Whether that's acceptable, or desirable, is something only some human can decide. There is no technical reason for this to be right or wrong. It's all a matter of what you want as your outcome. Given that you haven't shown us what you want, we can only really guess.
(You might not be sure what you want, and that's OK too: just pick something you think will be good! As the Elves say, you have not told us all concerning yourself, and how then shall we choose better than you?)
1People using Git often say "I merged branch X", but in fact, we merge commits, with Git using some particular algorithms to achieve this. It's important to know how merge works: in the "easy" cases where Git gets everything right, you can get by without deeper knowledge, but in the "hard" ones where Git stops in the middle, this becomes much trickier.
The mechanics of making commit S
The way you make commit S
, in Git, is actually ridiculously easy. It takes just two commands:
git switch -c branch4 <hash-of-B>
git merge --squash branch3
That's it: those two commands build commit S
atop commit B
, given the diagrams I have drawn here. We first create a new branch name, branch4
, to hold commit S
, pointing to existing commit B
. We then use git merge
to compute the correct snapshot for new commit B
. This turns out to be trivial for git merge
because all the commits we're "merging" simply add on to existing commit B
; Git would have to do a bit more work if we wanted, for instance, to create commit S
right after commit E
(but this would in general still work).
The git merge --squash
code invokes Git's merge-as-a-verb action, the merge engine, to come up with the snapshot. But then it makes an ordinary commit, one with a single parent. We get to write the commit message for this new ordinary commit S
, which adds on to the current branch the way any new commit adds on to the current branch. When we're done, we have our new "squash" commit S
.
You can use this trick to make an "S"-type squash commit anywhere. There are other ways to make an "S"-like commit, including using git reset --soft
(to make it on the current branch) and the "squash and merge" button on GitHub, for instance. Squashing is a tool; it produces a particular result, just as a tool like a drill produces a particular result (a hole). Some particular result may be sometimes good (need a pilot hole for a screw, need a hole to insert a tube, etc) and sometimes bad (the hole lets all the fluid out, oops). Learn what your tools do, and how and when to use them.