2

It may be a pretty simple question, but if I understand the concepts correctly, I can't really grasp how are you supposed to create releases with CI.

Basically, if you merge unfinished branches into development branch for the CI (so everyone's code is as up to date as possible etc.), you're never really going to end up with a stable, ready to deploy version on your master, as there's always some unfinished work going on.

How are you meant to create a deployment version if you keep updating your master with unfinished features?

The only idea I had was using cherry-pick or doing an additional pull request to stable branch only when the feature is finally finished, but both of these approaches seem wrong.

I read something about not merging unfinished features, and only merging master into the unfinished branches, but wouldn't that cause the very problem CI is trying to prevent? As in, if you have 2 big feature branches being worked on, before they get finished you have no changes to the master, and then you have 2 huge pull requests from both features being complete.

Am I missing something obvious? Thanks!

3 Answers 3

3

The idea is you always keep it stable. That's different from having every feature complete. It means the incomplete features don't impact the user experience and don't break other features. The most common way we do that at my work is by having a configuration item that turns it off by default.

Sometimes something might be running in the back end, but there isn't a front end page for it, or the url works but isn't linked to from anywhere, or it's only accessible to invited users, or you have to opt in to a beta feature, or you have to add a special version id to your request.

Making features optional like that feels like more work, but it helps make your development much more incremental. It lets you demo features a little bit at a time and get feedback before you waste too much time going the wrong direction. It's much more stable than waiting to merge a feature until it is completely finished.

4
  • I never thought about it this way. So you're saying you just deploy an update including unfinished (but stable) features, but just disabled? I assumed it was a bad practice to deploy "unnecessary" stuff, but that was just a feeling, not actual knowledge. Feb 18, 2021 at 2:18
  • 1
    It's not "deployed" until customers are using it by default. Feb 18, 2021 at 2:40
  • 1
    @AdamJacyno Disabling the unfinished features is usually done with so-called "feature flags" - that search term should bring up a whole host of related content
    – mmathis
    Feb 18, 2021 at 3:09
  • 2
    @AdamJacyno The methodology you're looking for is called Continuous Delivery. The Humble & Farley book of the same name says "Shipping semicompleted functionality along with the rest of your application is a good practice because it means you're always integrating and testing the entire system as it exists at any time" (p348).
    – bdsl
    Feb 18, 2021 at 20:42
3

Continuous integration doesn't work if you are blindly merging unfinished branches into the development branch. Instead, your work should be defined in small, discrete chunks that can be merged into the upstream branch and have automated tests run to reduce the chances that the change introduced a regression. It should take no more than a few hours to a day in order to develop the unit of work, including creating and updating the automated tests associated with it. It may take several of these commits (a few days of work) in order to have something with user-facing value. Continuous integration gives you confidence that every step you take is integrated and works with the steps taken by other people on the team.

I'd also point out that continuous integration is not continuous delivery or continuous deployment. Although each commit should result in a buildable and testable piece of software, it doesn't need to be deliverable to a downstream entity or deployable to production as-is. There are other techniques that can be used to ensure that software is always deliverable or deployable with features in various states of development.

4
  • Oooh, I see, that makes much more sense. Though, it raises a question, should the chunks be based on user POV (so the chunk should be a visible and finished user functionality), or just "stable" chunks? And if it should be just a "stable" chunk, then how should you go about stable, but still unfinished functionalities? Is there a standard solution for that, or is it situational (for example what Karl Bielefeldt said in his answer) Feb 18, 2021 at 2:16
  • @AdamJacyno For continuous integration, it doesn't matter since you aren't doing more than building and running automated tests - there is no deployment. If you're doing continuous delivery or continuous deployment, you can use techniques like feature flags, dark launching or keystone interfaces to make sure that users only see "finished" functionality, perhaps even based on the environment.
    – Thomas Owens
    Feb 18, 2021 at 10:48
  • Thank you, after a quick glance at the articles you linked it seems like they explain how to deal with exactly the situation I'm thinking about. Speaking of CD, while for the most part CD toolset seems to be helpful to me, I'm not specifically looking for CD, because I don't want my code to be deployable at all times, I just want to be able to release it at specific times, but it just seems like the best way to do it... is to implement CD in the end, so I guess I'll do it. Feb 18, 2021 at 13:49
  • 1
    @AdamJacyno If you want your code to be potentially releasable at all moments in time, then you want continuous delivery. If you want your code changes to be released as they are made, then you want continuous deployment. Continuous deployment builds on continuous delivery, which builds on continuous integration.
    – Thomas Owens
    Feb 18, 2021 at 16:09
2

Basically, if you merge unfinished branches into development branch for the CI (so everyone's code is as up to date as possible etc.), you're never really going to end up with a stable, ready to deploy version on your master, as there's always some unfinished work going on.

Well, yes. If you realize that having mud on the floor makes your house inappropriate for dinner parties, and you then specifically let people come into the house with dirty shoes because you want them to be inside as soon as possible, then the house is going to be in a perpetual state of being inappropriate for dinner parties.

How are you meant to create a deployment version if you keep updating your master with unfinished features?

Well, you can't. Not unless you want to deploy those unfinished features, which you obviously don't want.

The solution here is to exercise enough patience so that people can clean their shoes. Or, to drop the analogy, only merge completed, tested and reviewed work.

Continuous integration doesn't mean that you don't care about the stability of the thing you're integrating. It just means that you don't shelve things after they've been completed.

(so everyone's code is as up to date as possible etc.)

I don't quite understand why you're interested in merging unfinished work. If you already need to depend on those incomplete implementations, then you've subdivided your tasks poorly. If dev A can't work until they can depend on dev B's work, which is in progress and not stable yet, then you should've waited with giving dev A this task until dev B was finished.

In rare cases where dev A and B need to work simultaneously, A should simply work with B on the same feature branch (depending on the impact they could sub-branch it if necessary). In that sense, A and B's task is actually one big task, and thus one feature branch. What they do in that branch, and if they're able to work with each other's unfinished work, that's their business.

But in the meanwhile, your master/dev branch must stay clean at all times.

I read something about not merging unfinished features, and only merging master into the unfinished branches, but wouldn't that cause the very problem CI is trying to prevent?

CI naturally aligns itself with small tasks and short-lived feature branches. And you are correct that it's more ideal to work with small tasks here. But in reality, some big changes are just inevitable. Or, more correctly, further subdividing them would create more paperwork and branch juggling than it would help your development.

In these cases, the best you can do is have your feature developer frequently merge master into their feature branch. This significantly lowers the likelihood of having huge merge conflicts when you eventually try to merge the pull request into master.

Personally, I only merge master into my feature branch when my code isn't broken up. That doesn't mean finished, it does mean that it at the very least compiles, and ideally without any broken tests. This is just to ensure that in case I run into merge issues, I am able to quickly build/test my branch to see if I fixed the merge issues.

As in, if you have 2 big feature branches being worked on, before they get finished you have no changes to the master

Well, your master shouldn't be updated if there hasn't been a new feature that's been completed. You should only have changes when, well, a change is ready for master. "Unfinished" inherently means "not ready".

and then you have 2 huge pull requests from both features being complete

Big changes entail big pull requests. That's why we call them big changes. I'm not quite sure what your issue is here.

If it's about the size of the changes, then compartmentalize your development tasks more finely, so you get more but smaller tasks, and thus more but smaller pull requests.

If it's about the timing of it, i.e. everyone creates a pull request at the end of the sprint, that's sort of unavoidable; since you should only create a pull request when you're done and an ideally planned sprint means that people are finishing their tasks when near the end of the sprint.

How you resolve such a timing conflict depends on what you're able to do. Maybe avoid doing two invasive changes (i.e. those total makeover kind of change) at the same time if you can.
Or you can simply dedicate a final piece of your sprint purely to PR reviewing, testing, refactoring and merge conflict resolution.

One company I worked at used 3 week sprints, and the last 2 days were "non development" in the sense that they expected your PR to be up by the third Wednesday, and then the devs shifted gear into reviewing/fixing/merging/retrospective. In cases where there simply wasn't enough conflict to keep people occupied for two days, there's always some low prio bug that can be addressed, or small POC to work on, which is not essential to the sprint's completion if you can't complete it before the sprint end.

5
  • I would want to finish stable but unfinished work, to avoid huge pull requests later. The main example where I would find CI problematic is when you make a huge new system - it has a ton of separate stable points where you can leave it, but until you finish it ALL, NONE of it works - so you can't deploy it, at least not visibly. And waiting for the whole system to be finished with a merge seems like the thing CI is supposed to avoid. I also have master as a dev branch and a separate stable branch, so there's that. So far seems feature flags / feature toggle is the most common solution I guess. Feb 18, 2021 at 13:53
  • @AdamJacyno: That is indication of a badly subdivided task list. CI doesn't avoid merging, it merely deincentivizes massive merges in favor of more frequent little ones. But if you create massive tasks and features, then no amount of CI is going to stop your merges from being massive. In other words, don't make a "huge new system". Take several independent steps towards building what will eventually become a huge system. This kind of "bite size" approach is precisely what CI is all about. But it requires you to actually subdivide your tasks, which it sounds like you're not doing.
    – Flater
    Feb 18, 2021 at 14:11
  • I think we have a misunderstanding. I do divide big tasks into smaller ones, but stuff will not make sense / be usable for the user until the whole system is finished. Having a banking app which only let's you pay, and not receive money, makes no sense, so you should probably wait with releasing paying functionality until you finish receiving one, right? Until then you would not deploy or just hide the paying functionality Feb 18, 2021 at 14:32
  • @AdamJacyno: There is a vast difference between releasing something to a customer and deploying something on your dev server. CI/CD can be focused on dev/acc and not prod. Also, if you can pay people but they can't receive, who is receiving the existing payment? A banking app is a really bad example here.
    – Flater
    Feb 18, 2021 at 17:16
  • Yeah, maybe it was not a great example but I think you got the point. Yeah, there is a big difference between prod and dev server indeed, I understand how to put stuff onto the dev server, I was missing how to actually put stuff on prod when you have unfinished (although stable, passing tests) features on your dev branch. Feb 18, 2021 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.