├── .bumpversion.cfg ├── .github └── workflows │ └── pdf.yml ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── diagrams ├── git-tag-flow.xml └── gtfo.xcf └── images ├── FeatureFlow.png ├── HotFix.png ├── MonoRepo.png ├── MultipleReleases.png └── gtfo.png /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | tag_name = release/{new_version} 3 | current_version = 0.0.7 4 | commit = True 5 | tag = True 6 | 7 | [bumpversion:file:README.md] 8 | -------------------------------------------------------------------------------- /.github/workflows/pdf.yml: -------------------------------------------------------------------------------- 1 | name: Make pdf for every release 2 | on: 3 | push: 4 | tags: 5 | - release/* 6 | jobs: 7 | convert_to_pdf: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Check out markdown 11 | uses: actions/checkout@v2 12 | - name: Create files list 13 | id: files_list 14 | run: | 15 | mkdir output 16 | echo "::set-output name=files::README.md" 17 | - name: Get release version 18 | id: release_version 19 | run: | 20 | echo "::set-output name=release_version::$(echo ${{ github.ref }} | cut --delimiter=/ -f4)" 21 | - name: Convert to pdf 22 | uses: docker://pandoc/latex:2.14.2 23 | with: 24 | args: >- 25 | --output=output/gtf-${{ steps.release_version.outputs.release_version }}.pdf 26 | ${{ steps.files_list.outputs.files }} 27 | - uses: actions/upload-artifact@v2.2.4 28 | with: 29 | name: gtf-${{ steps.release_version.outputs.release_version }}.pdf 30 | path: output/gtf-${{ steps.release_version.outputs.release_version }}.pdf 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eww generates these for md preview 2 | *.html 3 | README.pdf 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | ### Changed 13 | 14 | ### Fixed 15 | 16 | ### Removed 17 | 18 | ## [1.0.0] 2021-10-18 19 | 20 | ### Added 21 | * everything! 22 | 23 | ### Changed 24 | 25 | ### Fixed 26 | 27 | ### Removed 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # make sure we have bumpversion installed 2 | ifeq (, $(shell which bumpversion)) 3 | $(error "No bumpversion in $(PATH), consider doing pip install bumpversion") 4 | endif 5 | 6 | version/patch: ## patch release 7 | bumpversion patch 8 | 9 | version/minor: ## minor release 10 | bumpversion minor 11 | 12 | version/major: ## major release 13 | bumpversion major 14 | 15 | 16 | .DEFAULT_GOAL := help 17 | .PHONY: help 18 | help: 19 | @grep --no-filename -E '^[a-zA-Z_\/-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Tag Flow (GTF) Version 0.0.7 2 | 3 | ![GTF](images/gtfo.png) 4 | 5 | Git Tag Flow is an alternate, convention based workflow to gitflow and trunk 6 | based work flows. It combines the best (lightest) features of both, and provides 7 | and opinionated, yet simple, deployment and release strategy via git tags and 8 | the principle of GitOps. 9 | 10 | 11 | ## Why another git work flow? 12 | 13 | Git flow is heavy and even considered legacy by 14 | [some](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). 15 | Heavy in that it requires maintenance of long lived branches and requires strict 16 | conventions when performing releases via release branches. 17 | 18 | Trunk based develops suggests that your architecture should change in order to 19 | facilitate being able to deploy potentially "broken" functionality into 20 | production. 21 | 22 | GTF takes the simplicity of trunk and applies *some* of the wisdom from gitflow, 23 | enabling GitOps via tags and enabling traceability of actions back to atomic 24 | commits. The principle is simple: you should always be able to trace back 25 | exactly what you deployed to production, within your git repository. This is not 26 | only common sense, but makes replicating production issues locally a less 27 | painful process. 28 | 29 | ## When should I use git tag flow? 30 | 31 | GTF is ideal for microservices, or solutions where the artefacts produced are 32 | 1-1 with the repositories. For example, if you maintain several repositories 33 | that each build a single docker image and maintain a deployment via another git 34 | repository, say via a `docker-compose.yml` file, then git-tag-flow is ideal. 35 | 36 | GTF is not limited to container solutions of course. 37 | 38 | Consider a classic web application with a REST-full backend, a Single Page 39 | Application (SPA) and infrastructure-as-code (IAC) definition that deploys to a 40 | cloud environment, each hosted in their own Git repository. Using git-tag-flow, 41 | the backend and front-end repositories will both produce their own distinct 42 | artefacts, the infrastructure-as-code deployment will deploy the vetted and 43 | versioned artefacts to the target estate. 44 | 45 | It can also work with mono-repos, however it comes with some caveats which are 46 | likely already accepted regardless of workflow. 47 | 48 | ## Conventions 49 | 50 | ### Branches 51 | 52 | Single main (trunk) line of development. This is the only permanent branch, and 53 | should have controlled access. 54 | 55 | short-lived branches that are used for working on various code types. These are 56 | left up to the discretion of the user, but some typical examples would be 57 | 58 | - `feature/some-feature` 59 | - `bugfix/JIRA-1234` 60 | - `chore/update-changelog` 61 | - `thys/perhapsthisworks` 62 | 63 | For deployment repositories, an extra shortlived branch is required that is used 64 | to deploy your solution from, typically this is called `staging/` 65 | 66 | ### Tags 67 | 68 | Tags are used to trigger events within git-tag-flow. But it is up to you to 69 | decide what actions are performed when a tag is pushed to a repository. 70 | 71 | **Note** *Because of this requirement, a CICD solution is required that can be 72 | triggered from git tags* 73 | 74 | By convention, the base tag recommended is `release/`, this allows your build 75 | pipeline to be able to detect tags and allows you to use whatever follows the 76 | `release/` stubb to control the flow of build events. 77 | 78 | For example, given a frontend SPA application. Tagging the main branch with 79 | `release/1.0.0` might cause your build pipeline to create a zip file artefact 80 | called `spa-1.0.0.zip` or perhaps build and push a docker image called 81 | `spa:1.0.0` - it is up to you on how to react to these events. Some command 82 | scenarios are listed below. 83 | 84 | For a deployment repository, things often get slightly more complicated due to 85 | the nature of maintaining multiple environments eg dev, test, qa & prod. A 86 | simple solution to this problem is to extend the tag to include an environment. 87 | 88 | For example, `release/dev/1.0.0` placed on the deployment repository would cause 89 | your build pipeline to deploy version `1.0.0` to the `dev` environment, assuming 90 | your pipeline is able to parse the tag values. This deployment repository also 91 | assumes that a manifest exists that defines the correct version of the SPA and 92 | backend components, like a `docker-compose.yml` file or VM cloud init script. 93 | 94 | You can of course tailor the tag to contain whatever information you require, 95 | if this basic structure doesn't fit your needs. It's entirely up to you, as 96 | long as you can parse the structure and use it. Keep in mind that the purpose 97 | of a git tag, especially a `release/` tag in GTF is mark when an event happen, 98 | so that you can trace a deployment back to your source code. Putting more than 99 | just 'facts' like environments etc in there, might make this traceability less 100 | deterministic. 101 | 102 | ### Deployment and Service repositories 103 | 104 | In a multi-repository setup, your typical setup might be a single git repository 105 | per service, which produces a single artefact, be that a docker image or other. 106 | 107 | Multiple of these services would exist within a given solution and the combination 108 | of them communicating comprises what makes up a deployment. 109 | 110 | Typically a single git repository exists that defines what versions of what 111 | services define a given release. In this document it is referred to as a 112 | *deployment manifest* but you can think of it as a simple `docker-compose.yml` 113 | , when using GTF with docker services. Of course it could also be much 114 | more complicated, depending on your release process. 115 | 116 | ### Restrictions on Tag placement 117 | 118 | Tags should always trigger a pipeline, regardless of where they are placed 119 | within version control. The reason for this is that managing multiple releases 120 | is a very real thing and that is why the deployment repository specifies a 121 | `staging/` branch convention. 122 | 123 | When managing multiple `staging/` branches, release tags can be placed on any 124 | commit and *ideally* a deployment will occur. When managing a single release, 125 | then the release tag can be placed on the main branch with the same effect. It 126 | is however, good practice to place all releases within a `staging/` branch so 127 | that pull requests (and pull request specific pipelines) can occur. 128 | 129 | You might ask, "Why is `staging/` the defacto name for a what is essentially a 130 | release branch". The answer is that naming a branch the same as a tag 131 | can cause confusion. For example, `git checkout release/1.0.0`: are we 132 | checking out a tag or branch here? For this reason `staging/` is the branch 133 | name that is used to trigger a release, when the `release/` tag is applied. 134 | 135 | ## Interaction with changelogs 136 | 137 | git-tag-flow doesn't believe in automatic generation of changelogs. Automatic 138 | generation of changelogs is not a deterministic action and changelogs **should** 139 | be managed by the developer, and treated as a change to the code base in which 140 | it lives. 141 | 142 | That being said, there is nothing to stop you generating a changelog during a 143 | build event. 144 | 145 | ## Scenarios 146 | 147 | The following are scenarios that explain the typical actions that happen within 148 | a development lifecyle, when using tag flow. Within all the examples, it is 149 | assumed that a pipeline is present that is capable of triggering an action 150 | based on the presence of a tagged commit. 151 | 152 | ### Feature Flow 153 | 154 | The most basic and day to day flow does not differ from any other workflow. 155 | A developer or set of developers work in a seperate branch, say a `feature/` 156 | branch that has been forked from the main branch at some point. 157 | 158 | Where GTF comes into play is that users can, if they require, release their 159 | code and trigger a build of their service artefact by applying a `release/` 160 | tag. 161 | 162 | This is not the normal behaviour, and usually reserved for hot fix events, 163 | or similar. However it *is* possible. 164 | 165 | The standard behaviour would be, developer codes feature in feature branch, 166 | then applies whatever merge strategy to merge into the master branch. Once 167 | a release is required/ready, then the main branch can be tagged with say 168 | `release/1.0.0` to trigger a build of that service. This service artefact 169 | can then be referenced within a deployment manifest. 170 | 171 | ![Feature Workflow](images/FeatureFlow.png) 172 | 173 | **note** for purists, who don't want to mess with the main branch, a 174 | temporary branch can be created to prep the release, apply the tag and 175 | merge into master. This is the way, if you have main branch permissions 176 | that don't allow direct interaction via a user. 177 | 178 | ### Deploying to environment via tag 179 | 180 | For reference, this particular build pipeline extract is for Azure Devops 181 | pipelines. 182 | 183 | ```yaml 184 | variables: 185 | - name: isMain 186 | value: $[eq(variables['Build.SourceBranch'], 'refs/heads/master')] 187 | - name: isReleaseTag 188 | value: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/release/')] 189 | 190 | trigger: 191 | tags: 192 | include: 193 | - release/* 194 | 195 | steps: 196 | - script: | 197 | # removes the refs/tags/ 198 | export GIT_TAG=${BUILD_SOURCEBRANCH:10} 199 | export ENVIRONMENT=$(echo ${GIT_TAG} | cut --delimiter=/ -f2) 200 | export RELEASE_VERSION=$(echo ${GIT_TAG} | cut --delimiter=/ -f3) 201 | export DOCKER_REGISTRY_PASSWORD 202 | export DOCKER_REGISTRY_USERNAME 203 | export DOCKER_REGISTRY_FQDN 204 | make -e deploy 205 | env: 206 | DOCKER_REGISTRY_PASSWORD: $(REGISTRY_PASSWORD) 207 | DOCKER_REGISTRY_USERNAME: $(REGISTRY_USERNAME) 208 | DOCKER_REGISTRY_FQDN: $(REGISTRY_FQDN) 209 | displayName: Deploy... 210 | condition: eq(variables.isReleaseTag, true) 211 | ``` 212 | 213 | ### Hot Fix 214 | 215 | Say the current production deploy is 1.0.0. We have a multi-repo solution, 216 | and the manifest looks similar to the following. 217 | 218 | ```yml 219 | version: 1.0.0 220 | deploy: 221 | frontend: 222 | version: 2.0.0 223 | 224 | backend: 225 | version: 2.1.0 226 | ``` 227 | 228 | In this scenario the frontend service requires a hotfix to the production 229 | deployment. 230 | 231 | A developer working on the frontend will create a branch from `release/2.0.0` 232 | to `hotfix/fix-urgent-bug` 233 | 234 | Apply the fix and commit. Assuming testing is all good, then the new release 235 | for the frontend service can be triggered directly from this branch by applying 236 | the tag, `release/2.0.1`, creating the new service artefact in the process. 237 | 238 | Now, in the deployment repository, we branch from the current prod release 239 | tag `release/prod/1.0.0` into a branch called `staging/1.0.1`. *Notice 240 | the staging branch doesn't have an environment indicated, that's because with 241 | GTF any staging branch can potentially release to any environment - as long 242 | as your pipline is setup that way*. 243 | 244 | The deployment manfiest is updated to reflect the bump in the frontend service, 245 | 2.0.1 and production version bump. 246 | 247 | ```yml 248 | version: 1.0.1 249 | deploy: 250 | frontend: 251 | version: 2.0.1 252 | 253 | backend: 254 | version: 2.1.0 255 | ``` 256 | 257 | The commit is then tagged to be deployed to production via, `release/prod/1.0.1` 258 | which triggers a production deployment with the new service. 259 | 260 | The staging branch `staging/1.0.1`, is then merged back into main branch. 261 | 262 | ![HotFix Workflow](images/HotFix.png) 263 | 264 | 265 | ### Managing multiple releases 266 | 267 | There's no easy way to manage multiple releases without at least a little bit 268 | of pain. When you have 2 or more active releases, all in various states of being 269 | deployed to an environment, at some point all of the `staging/` branches will 270 | need to be merged into the main branch. This process is straight forward if 271 | your release versioning is linear, but what if they aren't? When it comes 272 | time to merge, you might lose the history of the deployment manifest. 273 | 274 | Within a multi-repo solution, say the current production deploy is 1.0.0 and 275 | the team is busy with a UAT cycle for an upcoming 2.0.0 release. 276 | 277 | Consider a deployment manifest for the existing production deploy, version 1.0.0 278 | that looks like this. 279 | 280 | ```yml 281 | version: 1.0.0 282 | deploy: 283 | frontend: 284 | version: 2.0.0 285 | 286 | backend: 287 | version: 2.1.0 288 | ``` 289 | 290 | For simplicities sake, the development team has been busy pushing ahead with 291 | a major release, and has bumped all the service versions up by a major version. 292 | 293 | ```yml 294 | version: 2.0.0 295 | deploy: 296 | frontend: 297 | version: 3.0.0 298 | 299 | backend: 300 | version: 3.0.0 301 | ``` 302 | 303 | A production bug is found within the version 1.0.0 release, which needs to be 304 | addressed. It's a significant bug, so will require some quick testing in a lower 305 | environment before being deployed to production. 306 | 307 | Within the frontend service repository, a developer has created a fix and 308 | applied tag `release/2.0.1`, creating a new 2.0.1 version artefact in the process. 309 | 310 | In the deployment repository, a branch is created from the `release/prod/1.0.0` 311 | tag, called `staging/1.0.x`. 312 | 313 | **Note:** The `x` is used in this case just to be clear that 314 | this branch could potentially release any patch level release. There is no reason 315 | not to lock it down to `staging/1.0.1`, but sometimes there can be multiple 316 | iterations before the bug is truely squashed. 317 | 318 | The developer updates the manifest to use the new version of the frontend - 2.0.1. 319 | 320 | ```yml 321 | version: 1.0.1 322 | deploy: 323 | frontend: 324 | version: 2.0.1 325 | 326 | backend: 327 | version: 2.1.0 328 | ``` 329 | 330 | Once this and any other changes like changelogs are updated, then the change is 331 | committed and tagged for release to a test environment, `release/test/1.0.1` 332 | 333 | Once the testing is approved, the same commit is tagged for release to production, 334 | `release/prod/1.0.1` and merged. 335 | 336 | Meanwhile, the other developers have been busy developing the next version of the 337 | production (v2.0.0) and have deployed to the uat environment several times. 338 | 339 | ![Multiple Releases Workflow](images/MultipleReleases.png) 340 | 341 | 342 | The rule of thumb when managing multiple releases is that once a release is deployed 343 | to the highest environment, production. It should be merged to the main branch. 344 | 345 | ### Container builds with latest and fixed version tags 346 | 347 | Example of a general purpose build pipeline that can create a `latest` docker 348 | image or fix version depending on the build condition. For reference, this 349 | particular build pipeline extract is from Azure DevOps. 350 | 351 | ```yaml 352 | 353 | variables: 354 | - name: isMain 355 | value: $[eq(variables['Build.SourceBranch'], 'refs/heads/master')] 356 | - name: isReleaseTag 357 | value: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/release/')] 358 | 359 | trigger: 360 | tags: 361 | include: 362 | - release/* 363 | 364 | steps: 365 | - script: | 366 | export DOCKER_IMAGE_TAG=latest 367 | export DOCKER_REGISTRY_PASSWORD 368 | export DOCKER_REGISTRY_USERNAME 369 | export DOCKER_REGISTRY_FQDN 370 | make -e docker-build 371 | make -e docker-push 372 | displayName: Build and Push Latest Image 373 | condition: or(eq(variables.isMain, true), eq(variables.isReleaseTag, true)) 374 | env: 375 | DOCKER_REGISTRY_FQDN: $(REGISTRY_FQDN) 376 | DOCKER_REGISTRY_PASSWORD: $(REGISTRY_PASSWORD) 377 | DOCKER_REGISTRY_USERNAME: $(REGISTRY_USERNAME) 378 | 379 | 380 | - script: | 381 | # 18 characters == refs/tags/release/ 382 | export DOCKER_IMAGE_TAG=${BUILD_SOURCEBRANCH:18} 383 | export DOCKER_REGISTRY_PASSWORD 384 | export DOCKER_REGISTRY_USERNAME 385 | export DOCKER_REGISTRY_FQDN 386 | make -e docker-build 387 | make -e docker-push 388 | env: 389 | DOCKER_REGISTRY_PASSWORD: $(REGISTRY_PASSWORD) 390 | DOCKER_REGISTRY_USERNAME: $(REGISTRY_USERNAME) 391 | DOCKER_REGISTRY_FQDN: $(REGISTRY_FQDN) 392 | displayName: Build and Push Release Image 393 | condition: eq(variables.isReleaseTag, true) 394 | ``` 395 | 396 | ### Generate pdf for every release 397 | 398 | In this scenario every `release/` tag triggers the generation of pdf version of 399 | the project's README.md. We use github actions here as this is where the GTF 400 | repo is currently hosted. 401 | 402 | ```yaml 403 | name: Make pdf for every release 404 | on: 405 | push: 406 | tags: 407 | - release/* 408 | jobs: 409 | convert_to_pdf: 410 | runs-on: ubuntu-20.04 411 | steps: 412 | - name: Check out markdown 413 | uses: actions/checkout@v2 414 | - name: Create files list 415 | id: files_list 416 | run: | 417 | mkdir output 418 | echo "::set-output name=files::README.md" 419 | - name: Get release version 420 | id: release_version 421 | run: | 422 | echo "::set-output name=release_version::$(echo ${{ github.ref }} | cut --delimiter=/ -f4)" 423 | - name: Convert to pdf 424 | uses: docker://pandoc/latex:2.14.2 425 | with: 426 | args: >- 427 | --output=output/gtf-${{ steps.release_version.outputs.release_version }}.pdf 428 | ${{ steps.files_list.outputs.files }} 429 | - uses: actions/upload-artifact@v2.2.4 430 | with: 431 | name: gtf-${{ steps.release_version.outputs.release_version }}.pdf 432 | path: output/gtf-${{ steps.release_version.outputs.release_version }}.pdf 433 | 434 | ``` 435 | 436 | 437 | ### Mono-Repos 438 | 439 | Git-Tag-Flow can work with mono-repos, however there are reasons why a mono repo 440 | might not always be [a](https://fossa.com/blog/pros-cons-using-monorepos/) 441 | [good](https://alexey-soshin.medium.com/monorepo-is-a-bad-idea-5e587e848a07) 442 | [choice](https://semaphoreci.com/blog/what-is-monorepo). 443 | 444 | This is just an example to mimic the multi-repository setup as much as possible. 445 | There could be any number of ways to achieve the same outcome. 446 | 447 | Consider the following monorepo structure. It contains the services `frontend` 448 | and `backend`, a `deploy` folder containing something that represents a 449 | deployment manifest. 450 | 451 | ```shell 452 | backend/ 453 | deploy/ 454 | frontend/ 455 | .git 456 | .gitignore 457 | README.md 458 | ``` 459 | 460 | With a multi-repo structure, each of the services would be within their own 461 | repository, so releasing a new build of the service is a matter of applying 462 | a simple tag like `release/1.0.0`. In the monorepo world, we can't apply this 463 | without assuming that all services are to be released at the same time, with 464 | the same version number. 465 | 466 | To move around this limitation, introduce an extra segment into the `release` 467 | tag structure to differentiate. Following on from the above example, to 468 | release a new version of the frontend service, you would apply the tag 469 | `release/frontend/1.0.0`. Similarly for the backend service. 470 | 471 | Your pipeline that reacts to this tag being applied obviously needs to be 472 | able to build accordingly, based on what service has been requested in the 473 | tag. This shouldn't be a problem, since this is exactly what is required 474 | when performing a deployment - at least in the multi-repo scenario. 475 | 476 | Note how in this example the service in this case matches the service 477 | folder name, this would make it very easy to create a generic pipeline 478 | that builds any service within a matched direction. 479 | 480 | So we can now build our services within the mono-repo independently, however 481 | we now have a problem when we want to release. Traditionally with GTF, 482 | we would tag the deployment repository with a tag like `release/prod/1.0.0` 483 | to indicate that we want to deploy the specific versions of the services 484 | to production. This tag structure, unfortunately, conflicts with the 485 | *new* structure for releasing an artefact of a given service, so we need to 486 | adapt. 487 | 488 | There's a few ways we could do this, we could naively adjust our pipeline 489 | to only look for pre-canned service folders specified within the `release/` 490 | tag, but this wouldn't scale as we'd have to constantly update the pipeline 491 | as you add new services. You could simply change your pipeline to react 492 | differently if the "deploy" folder is detected within the `release/` tag, 493 | ie `release/deploy/1.0.0`. This would work well, but could look confusing 494 | when looking through git history. 495 | Finally you could change the prefix of a deployment specific `release/` tag 496 | from `release/` to `deploy/` and keep the structure the same and at the 497 | same time be completely specific about what is a deployment and what is a 498 | build. 499 | 500 | #### Monorepo hotfix scenario 501 | 502 | Given all the above, a hotfix scenario in a mono-repo would be as follows. 503 | 504 | Assume the current production release is the following deployment-manifest, 505 | which is defined (somehow) within the deploy folder of the repo. Also assume 506 | for arguments sake that the chosen tag used to deploy this release was 507 | `deploy/prod/1.0.0` and that the release tags to build the service artefacts 508 | where `release/frontend/2.0.0` and `release/backend/2.1.0`. 509 | 510 | ```yml 511 | version: 1.0.0 512 | deploy: 513 | frontend: 514 | version: 2.0.0 515 | 516 | backend: 517 | version: 2.1.0 518 | ``` 519 | 520 | A hotfix is required within the frontend service, so the developer follows 521 | the standard procedure and branches from the `release/frontend/2.0.0` tag 522 | into a branch called `hotfix/fix-critical-bug`. 523 | 524 | Like the multi-repo hotfix scenario, the fix is significant so a quick test 525 | environment deployment is required before it can go to production. 526 | 527 | The developer fixes the code in the hotfix branch and commits the code. They 528 | then apply a release tag, to build the service so that it can be deployed. 529 | `release/frontend/2.0.1`. 530 | 531 | After the build is finished and the artefact exists, the deployment can begin. 532 | 533 | The developer creates a staging branch, forked from the existing deployment 534 | tag, `deploy/prod/1.0.0`, calling it `staging/1.0.1`. The developer edits the 535 | deployment manifest to indicate the new version of the frontend service is 536 | required. They also bump the version of the release to `1.0.1` - indicating 537 | a bugfix in the semantic versioning world. 538 | 539 | ```yml 540 | version: 1.0.1 541 | deploy: 542 | frontend: 543 | version: 2.0.1 544 | 545 | backend: 546 | version: 2.1.0 547 | ``` 548 | 549 | The developer wants to deploy to test, so the tag `deploy/test/1.0.1` is 550 | applied to the commit and pushed to trigger the deployment pipeline. After 551 | the fix has been tested, the developer can now apply and push the production 552 | tag, `deploy/prod/1.0.1` to the same commit. 553 | 554 | After the deployment is complete and confirmed in production, the hotfix 555 | branch is merged into the main branch, followed by the `staging/` branch. 556 | While all this is happening every other dev continues as per normal. 557 | 558 | The following diagram illustrates the process. 559 | 560 | ![Mono Repo Workflow](images/MonoRepo.png) 561 | 562 | 563 | It would be expected in this case that the CICD solution would create the 564 | frontend and backend artefacts, before the deployment procedure is called. 565 | This might result in two artefacts, `frontend-2.0.1.zip` and `backend-2.1.0.zip`, 566 | or similar with docker images `frontend:2.0.1` and `backend:2.1.0`. The point 567 | is, they must exist before a deployment can occur - because a build and 568 | deployment are two distinct actions. 569 | 570 | An example pipeline build in this example that would be to deploy a set 571 | of artefacts when triggered after a `deploy/` tag is detected. 572 | 573 | ```yaml 574 | variables: 575 | - name: isDeployTag 576 | value: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/deploy/')] 577 | 578 | trigger: 579 | tags: 580 | include: 581 | - release/* 582 | 583 | steps: 584 | - script: | 585 | # removes the refs/tags/ 586 | export GIT_TAG=${BUILD_SOURCEBRANCH:10} 587 | export ENVIRONMENT=$(echo ${GIT_TAG} | cut --delimiter=/ -f2) 588 | export RELEASE_VERSION=$(echo ${GIT_TAG} | cut --delimiter=/ -f3) 589 | export DOCKER_REGISTRY_PASSWORD 590 | export DOCKER_REGISTRY_USERNAME 591 | export DOCKER_REGISTRY_FQDN 592 | make -e deploy 593 | env: 594 | DOCKER_REGISTRY_PASSWORD: $(REGISTRY_PASSWORD) 595 | DOCKER_REGISTRY_USERNAME: $(REGISTRY_USERNAME) 596 | DOCKER_REGISTRY_FQDN: $(REGISTRY_FQDN) 597 | displayName: Deploy... 598 | condition: eq(variables.isDeployTag, true) 599 | ``` 600 | 601 | This pipeline could easily be extended to provide continuous deployment to a 602 | development server. For this, any change to the main branch could trigger a 603 | deployment and/or run CI tests. 604 | 605 | 606 | 607 | ## Best Practices 608 | 609 | ### Abstract pipeline code where possible into easier to run scripts/make targets. 610 | 611 | This isn't specific to git-tag-flow, but a good practice is to encapsulate as 612 | much as you can into scripts that can be run externally, not just within your pipelines. 613 | 614 | There will be times when you need to deploy manually, you don't want to be trying 615 | to recreate your pipeline logic in your developer environment in a high pressure situation. 616 | 617 | Another good reason to apply this logic, is that pipelines come and go, as does git 618 | hosting. Not being locked into a particular vendor can be a good thing. 619 | 620 | ### Alternatives to the default, `release/` tag and `staging/` branch names 621 | 622 | There's no firm rules around the tag and branching names for feature, bugfix, hotfix and 623 | chore ephemeral branches. That is left completely up to the developer. The only 624 | suggested branches and tag names are the `release/` tag and `staging/` branch. 625 | 626 | Some alternatives are to the above are `deploy/` tag and `release/` branch, it is 627 | entirely up to you and what makes sense for your situation. However you should ask 628 | yourself, does the verb and noun makes sense when it is applied to the tag and branch? 629 | 630 | This is especially true when using GTF with mono-repos. 631 | 632 | ### Embrace Fast Forward merge commits 633 | 634 | When using `staging/` branches within your deployment repository, if you apply multiple 635 | `release/` tags within that branch, then rebasing and squashing becomes hard, because 636 | you would lose the commits that those tags are attached to. For this reason, embrace 637 | merge commits and keep all that history. 638 | 639 | The same can't be said for your service repositories. If you aren't triggering builds 640 | from your feature/bugfix/hotfix branches, then there is no reason why you can't keep 641 | your commit history lean-and-mean. Rebase and squash is your friend here. 642 | -------------------------------------------------------------------------------- /diagrams/git-tag-flow.xml: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /diagrams/gtfo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/diagrams/gtfo.xcf -------------------------------------------------------------------------------- /images/FeatureFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/images/FeatureFlow.png -------------------------------------------------------------------------------- /images/HotFix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/images/HotFix.png -------------------------------------------------------------------------------- /images/MonoRepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/images/MonoRepo.png -------------------------------------------------------------------------------- /images/MultipleReleases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/images/MultipleReleases.png -------------------------------------------------------------------------------- /images/gtfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasdee/git-tag-flow/ca7ea5ce8fa8e135fd3925d9aa8ac6084581d5c2/images/gtfo.png --------------------------------------------------------------------------------