gitcode-formattingclang-formatgit-merge-conflict

How do I auto-format master and feature branches without causing conflicts?


Say I have this git history:

* 894a016 (HEAD -> feature) Add radius() method
* 6a62db6 (master) Add circle.h

Where circle.h in feature looks like:

#include <iostream>
using namespace std;

class Circle {
  double radius;

public:
  Circle(double r)
            : radius(
                r) {
  }
  double area() {
    return radius
          * radius
          * 3.14159265;
  }

  double radius() {
      return    radius;
  }
};

I want to format my entire repo, master and feature branches, without causing too many conflicts.

I can make new commits to any branch, but ideally would not need to rebase feature branches (if this is the only way that's fine—I'm doing this for a team so I'm just trying to keep things as painless as possible).

How do I format my trunk branch and my feature branches without creating unexpected conflicts?

Formatting both branches does not work, for example:

* 2e654d8 (HEAD -> feature) Run clang-format
* 894a016 Add radius() method
| * 25fab84 (master) Run clang-format
|/
* 6a62db6 Add circle.h

Results in conflicts when trying to merge. For example, git checkout feature && git merge master gives this conflict:

#include <iostream>
using namespace std;

class Circle {
  double radius;

public:
  Circle(double r) : radius(r) {}
  double area() { return radius * radius * 3.14159265; }
<<<<<<< HEAD

  double radius() { return radius; }
=======
>>>>>>> master
};

Solution

  • I think I found an acceptable answer. This works for this toy example, I'll do more tests and see if it works on a larger scale.

    Given this (slightly more complex) history:

    * c9ede4f (master) Add area to print()
    | * c859a1d (feature) Add radius to print()
    |/
    * 58ead7c Add print()
    * 427a8ba Add circle.h
    

    (this particular history actually gives good conflicts when formatting all branches, but I believe this solution works in the general case)

    1. Auto-format master (tag the commits before and after formatting for ease)

      $ git checkout master
      $ git tag before_auto_format
      $ clang-format -i *
      $ git commit -am "Run clang-format"
      $ git tag after_auto_format
      
    2. Merge the pre-format master into feature and resolve conflicts like normal

      $ git checkout feature
      $ git merge before_auto_format
      $ vi circle.h
      $ git add .
      $ git commit
      
    3. Merge after_auto_format into feature. Discard the changes and do the formatting manually.

      $ git merge after_auto_format --no-commit
      $ git checkout HEAD .
      $ clang-format -i *
      $ git add .
      $ git commit
      

    The final history looks like:

    *   09f2448 (feature) Merge tag 'after_auto_format' into feature
    |\
    | * e5e964b (tag: after_auto_format, master) Run clang-format
    * | 1ef1b9b Merge tag 'before_auto_format' into feature
    |\|
    | * c9ede4f (tag: before_auto_format) Add area to print()
    * | c859a1d Add radius to print()
    |/
    * 58ead7c Add print()
    * 427a8ba Add circle.h
    

    I believe this achieves the desired effect of syncing the feature branch with the trunk, auto-formatting both, and not causing unexpected conflicts.

    If anyone has better alternatives, I'm all ears.