1

I've been using git merge -squash master --strategy-option=theirs on my release/staging branches. It allows me (as I understand) to take a snapshot of the current master branch into the release branch, but without all the hundreds of commits (each a new feature/bug fix) - instead they're all rolled up into a single commit ('Release 20180624' and 'Patch 1' etc) so I can quickly rewind to a nice set release point if I need to.

My issue is, after the git merge -squash master --strategy-option=theirs command, it's not bringing in all the changes. I just ran the command, and the stage branch, came up with an error compiling. I compare the two files, and on master and stage the file is different:

Master:

... loads of imports ...
import { CommentsModule } from '../comments/comments.module';
... loads more imports ...
import { routing } from './myjobs.router';
import { CommentsModule } from '../comments/comments.module';

@NgModule({
  imports: [
    ...

It's a simple error, the CommentsModule is imported twice.

Stage:

... loads of imports ...
import { CommentsModule } from '../comments/comments.module';
... loads more imports ...
import { routing } from './myjobs.router';

@NgModule({
  imports: [
    ...

Why does this difference exist? Almost everything else is identical - but now I feel I can't really trust the process. I've noticed this a few times, usually a single change like this (assuming there's nothing I've missed) in a random file. This time it was caught early because it was a nice compile error - but I can imagine there are other changes that are harder to spot. My release and stage branches don't accurately reflect my codebase.

Am I using merge -squash incorrectly?

Joe
  • 6,773
  • 2
  • 47
  • 81

1 Answers1

3

The short answer is that git merge—particularly the to merge combination of changes since a merge base—works on line-oriented differences and is not smart enough to understand that something should only be imported once. Without -X theirs or --strategy-option theirs, if the change-sets don't match well, you will get a merge conflict. Using theirs tells Git to blindly assume that where there is a conflict, it should use "their" version, but that only affects conflicts. Even without theirs this can misfire. For instance, suppose you added an import early:

[your changes near the top of the file]
 some context here
+import { CommentsModule } from '../comments/comments.module';

Suppose they decided to include this module many lines later:

 different context here
+import { CommentsModule } from '../comments/comments.module';

Git now sees instructions to add this same line twice, in different places, so it does that.

Both regular merge and squash merge perform the to merge operation. The difference between them is at the end, when you or Git make the final commit. With a regular merge, the new commit has two parent commits: your current commit, and the other commit you whose ID you passed to git merge:

...--o--*--o--o--...--o   <-- yourbranch (HEAD)
         \
          o--o---...---o   <-- theirbranch

becomes:

...--o--*--o--o--...--o--M   <-- yourbranch (HEAD)
         \              /
          o--o---...---o   <-- theirbranch

Without --squash, this becomes instead:

...--o--*--o--o--...--o--S   <-- yourbranch (HEAD)
         \
          o--o---...---o   <-- theirbranch

Note that the "squash merge" commit is an ordinary (non-merge) commit: it's not a merge at all! The regular merge commit is a merge commit. (This is a little bit of circular logic: a merge commit is simply a commit with at least two parents. Still, that's the key distinction here. Other than this, a git merge --squash is an ordinary merge, although it also forces --no-ff and --no-commit.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • I see, essentially it's because it's tracking the changes as it goes. Thanks for the explanation. I guess I'm stuck with it, it does seem there's a way to diff branches https://stackoverflow.com/questions/9834689/comparing-two-branches-in-git so I can be assured that I can find all the differences at least! – Joe Jun 25 '18 at 14:29
  • Yes (though remember, each commit is a complete snapshot, not just a set of changes!). Note that `git diff A..B` is *exactly the same* as `git diff A B`: it tells Git to extract the commit identified by `A`, and the commit identified by `B`, and then compare these two snapshots. No branch relationship is required: just two commits. A branch name, in this context, refers to the tip commit on that branch—the one commit to which the branch name points directly. – torek Jun 25 '18 at 14:34