#[1]gnikyt feed [2]gnikyt / Code ramblings [3]Ty King [4]About[5]Github[6]LinkedIn[7]CV[8]RSS Git release branches / Mar 10, 2021 -- 3.4KB [9]Logo of python [10]Logo of cmd A method I like to utilize internally, which I've been slowly pushing into my open source work, is to create branches for each major/minor release. Many developers will say branches are meant to be short-lived, to be eventually merged into your main branch. I believe this is true for features and bugfixes, but there is a benefit to keeping branches around for the long term. Essentially, if you follow symver ([major].[minor].[patch]) for versioning, you would create and maintain a branch for each [major].[minor] release. For example... if I tagged the production branch with v9.0.0, a new major release, I would create a supplementary branch 9.0. If I then released a bugfix, v9.0.1, I would then issue a pull request to merge code for v9.0.1 into branch 9.0 to keep it updated with patches. $ git branch -l | sort -V # ... 8.2 8.3 9.0 develop master By having your releases tagged and supplementing it with release branches, you will: * Have the ability to rollback easily * Have the ability to support multiple versions easily * Have a easily navigable snapshot of previous releases This may be overkill for small and personal projects, but when you're working with a team and potentially have to support multiple versions of a codebase, then this flow is easy to adopt. Below is a script you can use to walk through your existing repository (assuming everything is tagged in symver format) and create the [major].[minor] branches for you. #!/usr/bin/env python # # Usage: `python release_branches.py` # For a "dry run": `DRY_RUN=1 python release_branches.py` # import subprocess import re from os import environ from typing import List, Dict def get_tag_list() -> List[str]: """ Get list of tags, sorted. """ # Ensure tags are in vX.Y.Z format mmp = re.compile(r"^v([0-9]+).([0-9]+).([0-9]+)$") # Get all tags that match the format tags = subprocess.getoutput(" ".join(("git", "tag", "-l"))).split("\n") result = list(filter(lambda tag: (mmp.match(tag) is not None), tags)) result = [tag[1:] for tag in result] result.sort(reverse=True) return result def major_minor_list(tags: List[str]) -> Dict[str, str]: """ Create a list of major.minor versions. """ mm = {} for tag in tags: # Strip off patch from tag cleaned_tag = ".".join(tag.split(".")[:-1]) if cleaned_tag not in mm: # Add to dict if major.minor doesn't exist yet mm[cleaned_tag] = tag return mm def make_branch(branch: str, tag: str) -> None: """ Make a branch from a tag. """ # Checkout tag, create branch of major.minor based on tag, push to BB subprocess.run(["git", "checkout", f"v{tag}"]) subprocess.run(["git", "checkout", "-b", branch]) subprocess.run(["git", "push", "origin", branch]) tags = get_tag_list() mm = major_minor_list(tags) for branch, tag in mm.items(): print(f"Making branch '{branch}' based on tag of '{tag}'...") if environ.get('DRY_RUN') is None: make_branch(branch, tag) print(">> Complete") Running with the DRY_RUN env supplied, you'll be able to see the branches it will created based on your existing tags before proceeding with the creation. Appendix This post is 5 years old and may contain outdated information. Copyright under [11]CC-4.0. Available in the following alternative formats: [12]MD / [13]TXT / [14]PDF * * * * * * * * References 1. /rss.xml 2. file:/// 3. file:///about 4. file:///about 5. https://github.com/gnikyt 6. https://linkedin.com/in/gnikyt 7. file:///assets/files/cv.pdf 8. file:///rss.xml 9. file:///category/python 10. file:///category/cmd 11. https://creativecommons.org/licenses/by/4.0/ 12. file:///git-release-branches/index.md 13. file:///git-release-branches/index.txt