I will have to create a second app based on the first app. The second app shares 98% of the code (Some of the UI is different). I am wondering how to set up the second app without creating a merging hell. The files which are different are not in one place throughout the code tree. (The app is a flutter app - I don't think that information madders)
3 Answers
Git is a version control system, which means it's good at tracking the change history of your project. It's not at all suitable for handling different editions of your software.
This means you should not necessarily model your use case through Git, but should take a step back and think about why you need two almost identical apps.
Sometimes, you just need some small fixes for another program. Then, you could create patch files that can be applied to the original project. This can actually be modelled via Git: you record your patches as commits on top of the other project, and then rebase as the other project updates. However, you will necessarily get merge conflicts if the original project changes the modified code. This is a good thing! However, it quickly gets extremely annoying and tedious if you have larger patches.
You can reduce the opportunities for conflicts by keeping most of the patched logic in separate files, and only modifying the original project files to include or call into your separate files. You should also leave as much of the original code intact as possible. E.g. don't remove code that is unused in the patched edition.
Perhaps, you need to distribute different editions of your app, like a Basic and Pro version. At first glance, you might be tempted to take the Pro version, create a Git branch, and to remove all the code that's not supposed to be in the Basic version. But that's going to cause pain down the road. Instead, note that they are one application that are merely distributed/deployed in different configurations.
This difference should be applied at build time, not at coding time. E.g. in C, I might use
#ifdef PRO_VERSION
to include some code in the deployed application only if a certain build-time setting is enabled. In an OOP context, dependency injection has similar effects.If you want ifdef-style source manipulations but are not using C, you could write some build scripts that assist with this. E.g. to create a release build, you could copy your entire source code into a separate directory, and then use a script to strip out parts of the files that shouldn't occur in that edition. Then you build it. The parts that should be removed could be demarcated by special comments. Tip: put the removed code into a comment or replace it with blank lines, in order to keep line numbers intact.
Perhaps you need to quickly create many distinct apps that only differ in small details. In particular, if multiple apps just have different branding, or small changes in business logic.
In that case, you actually want to create a library or framework that makes it easy to create new apps. In simple cases, a single app that just loads a configuration file to apply specific settings is sufficient. In more complicated cases, you might need a full-blown plugin system. In any case, the idea is to recognize what kind of things you need to change between apps, and to make those things configurable instead of hardcoding them. This changeability would be a core requirement of your software.
There are some drawbacks to this approach. If you want to be able to update your shared library (which contains the main app code), you need to pay close attention to backwards compatibility. In some cases, a potential solution is to use a monorepo approach, that all apps and the shared library are developed together. That way, you can modify the shared library and in the same step also update all dependent code accordingly.
I didn't always know about this. I worked on a project where we needed different editions of the software for different clients. We couldn't use solution #2 because the clients got the source code. We should have used solution #3 and created a plugin system for customer-specific changes. We actually created different Git branches, which wasted an insane amount of effort (I'd guess ~20% of the entire development effort). The different editions diverged more and more, and were basically impossible to merge after a few months. It was also difficult to implement cross-cutting concerns, like changing the CI configuration for all editions.
-
1I hear of people creating specific versions per customers, and having 50 versions in 50 branches. And I think... oh my god. Commented Apr 1, 2021 at 20:10
Here’s what I’ve done for an app with five versions: Create a configuration file for each version, each with a different name. Create five build targets. Each build target includes the config file, and then builds one version. Everything is checked into the same repository in git.
Say two of five versions want feature X. Two config files contain #define FEATURE_X 1, three contain #undef FEATURE_X. Your source files obviously need to handle both cases.
Important: All the differences are in different files, so you have no merge problems. During development, since all versions should be working all the time, just switch between versions once a week.
If there are really only minor differences in the UI, then most likely the answers already given are the way to go.
However, you might also consider putting the core (common) functionality in a library, and have different projects for the different applications/UIs making use of that library. This makes most sense when the UI is basically a very thin layer above the core functionality, and different versions of the UI have little in common with each other.