Git, GitHub & Version Control

Chapter 9
Releases, Dependencies and Repository Structures

Bachelors Program in Full Stack Development
Git, GitHub & Version Control   •   Bachelor

Releases, Dependencies and Repository Structures

Introduction

A software repository is more than a directory containing source code. It is a controlled record of a product’s history, dependencies, release decisions, deployment assumptions, and collaboration practices. In full-stack development, repository design becomes especially important because a single product may contain browser code, server code, database migrations, infrastructure configuration, documentation, shared libraries, generated assets, and deployment scripts.

This chapter examines how teams transform Git history into dependable releases and how they choose structures for organizing related code. It focuses on semantic versioning, annotated tags, release notes, changelogs, dependency management, monorepos, multirepos, submodules, subtrees, and Git Large File Storage (LFS). The central principle is that repository structure should make ownership, change impact, testing, release responsibility, and deployment boundaries clear.

Learning Objectives

By the end of this chapter, you should be able to:

  • apply semantic versioning to compatible and incompatible changes;
  • create and verify annotated Git tags;
  • distinguish release notes from changelogs;
  • manage direct, transitive, development, peer, and optional dependencies;
  • compare monorepo and multirepo architectures;
  • explain the appropriate uses and limitations of submodules, subtrees, and Git LFS;
  • evaluate repository structures for full-stack systems, shared libraries, and deployment environments;
  • design a release process that is reproducible, auditable, and suitable for team delivery.

1. From Commits to Releases

A commit records a change, but a release is a deliberate statement that a particular state of the code is suitable for consumption. A release normally identifies:

  1. a precise source state;
  2. a version identifier;
  3. a tested artifact or deployment;
  4. a description of changes;
  5. known risks, migration instructions, and compatibility information.

A release should therefore be reproducible. If a team cannot determine exactly which source revision produced a deployed service, incident investigation and rollback become difficult. Git tags provide a human-readable name for an immutable point in repository history, while release systems commonly attach binaries, container references, checksums, and notes to that tag.

A useful distinction is:

  • Commit: an individual historical change.
  • Branch: a movable reference to a line of development.
  • Tag: a normally stable reference to a specific object, often representing a release.
  • Release: a communication and distribution event built from a selected source state.
  • Artifact: a deployable or distributable output, such as a package, container image, or compiled bundle.

A tag alone does not guarantee quality. It identifies a point; tests, review, build provenance, and release policy establish confidence in that point.

2. Semantic Versioning

Semantic Versioning, commonly written as SemVer, provides a convention for communicating compatibility through version numbers. A normal version has the form:

MAJOR.MINOR.PATCH

For example, 3.4.2 has MAJOR = 3, MINOR = 4, and PATCH = 2. The conventional rules are:

  • increment MAJOR when an incompatible public API change is introduced;
  • increment MINOR when backward-compatible functionality is added;
  • increment PATCH when backward-compatible defects are corrected.

Pre-release identifiers, such as 2.0.0-beta.1, indicate that a version may not yet satisfy normal production-release expectations. Build metadata, such as 2.0.0+build.184, may identify build information but does not determine precedence between otherwise equal versions.

2.1 Compatibility is a Contract

Semantic versioning applies to a defined public interface, not to every internal implementation detail. A library’s public interface may include functions, exported types, command-line flags, configuration keys, database schema behavior, and documented error contracts.

Examples:

ChangeTypical version consequence
Fix an internal null-check without changing behaviorPATCH
Add an optional API fieldMINOR, if clients remain compatible
Add a new endpointMINOR
Remove or rename a public functionMAJOR
Change a response field from number to stringUsually MAJOR
Fix a security vulnerabilityDepends on compatibility; often PATCH, with urgent communication

A change can be operationally serious without being a major-version change. For example, a compatible performance regression may require urgent release communication but does not necessarily alter the API contract. Conversely, a small-looking rename can be a major change if clients depend on it.

The meaning of “breaking” also depends on the ecosystem. A change that is compatible for an application may be incompatible for a library with many external consumers. Teams should document their compatibility policy rather than treating SemVer as an automatic classification system.

2.2 Version Ranges and Lockfiles

Package managers often allow dependency ranges. A declaration such as ^4.2.0 expresses a permitted range according to the package manager’s rules, while a lockfile records the exact resolved versions and integrity information used in a particular installation.

These serve different purposes:

  • the manifest expresses acceptable compatibility;
  • the lockfile supports reproducible installation;
  • the registry or artifact repository distributes a selected version.

A lockfile should generally be committed for applications and services. Libraries may also commit one for development reproducibility, although consumers normally resolve the library’s dependencies within their own dependency graph. Teams must understand package-manager-specific rules rather than assuming that every caret or tilde range has identical behavior.

3. Annotated Tags and Release Integrity

Git supports lightweight tags and annotated tags. A lightweight tag is simply a reference to an object. An annotated tag is a separate Git object containing a tagger, date, message, and reference to the tagged object. Annotated tags are preferable for formal releases because they preserve release metadata and can be cryptographically signed.

Typical commands include:

git tag -a v2.3.0 -m "Release v2.3.0"
git push origin v2.3.0
git show v2.3.0

To sign a tag using configured signing tools:

git tag -s v2.3.0 -m "Release v2.3.0"
git verify-tag v2.3.0

A sound release process verifies that:

  • the tag points to the intended commit;
  • the working tree and build inputs are understood;
  • automated tests and security checks have passed;
  • the tag is protected from unauthorized rewriting;
  • the generated artifact can be traced back to the tag.

Teams should avoid casually moving or deleting published release tags. If a release is defective, a new patch version is usually clearer than silently changing the historical meaning of an existing tag.

4. Release Notes and Changelogs

A changelog is a continuing record of notable changes across versions. It is usually maintained in the repository and organized by version or release date. A release note is communication for a specific release and should emphasize user impact, migration requirements, known issues, and operational instructions.

A useful release note may contain:

  • release version and date;
  • highlights;
  • added, changed, fixed, deprecated, and removed behavior;
  • security notices;
  • database or configuration migration instructions;
  • compatibility warnings;
  • known limitations;
  • links to relevant issues or documentation.

Commit messages can support automation, but automatically generated notes require review. A technically correct list of commits may be incomprehensible to users, omit risk, or expose internal issue details. Release communication should be audience-aware: developers need API and dependency detail, operators need deployment and rollback information, and end users need behavior and impact.

A common workflow is:

  1. merge reviewed changes;
  2. calculate or propose the next version;
  3. run tests, linting, security scanning, and packaging;
  4. generate a draft changelog;
  5. review compatibility and migration information;
  6. create an annotated tag;
  7. build and publish artifacts;
  8. publish release notes;
  9. verify deployment and record the outcome.

5. Dependencies as a Software Supply Chain

A dependency is an external component required by a project. Dependencies may be direct, such as a framework explicitly declared by an application, or transitive, such as a library required by that framework. They may also be classified by purpose:

  • Runtime dependency: required when the software executes.
  • Development dependency: required for testing, linting, or building but not normally for production execution.
  • Peer dependency: expected to be supplied by the consuming project, often used by plugins and libraries.
  • Optional dependency: used only when a feature or platform requires it.
  • System dependency: supplied by the operating system, database, browser, or cloud environment.

Dependency management involves more than installing packages. Teams must evaluate license obligations, security advisories, maintenance activity, transitive risk, compatibility, and reproducibility. A dependency update can alter behavior even when application code is unchanged.

5.1 Direct and Transitive Risk

Suppose an application directly depends on library A, and A depends on B. The application has a direct dependency on A and a transitive dependency on B. Vulnerability reports may identify B even though the application team did not explicitly select it. The team must then determine whether to update A, override B where supported, patch or replace the dependency, or mitigate exposure operationally.

Useful controls include:

  • committed lockfiles;
  • automated dependency update proposals;
  • software composition analysis;
  • vulnerability and license scanning;
  • dependency review in pull requests;
  • provenance and signed artifact verification where available;
  • removal of unused packages;
  • periodic upgrade windows.

Pinning every dependency permanently is not a complete security strategy. It improves reproducibility but can leave known vulnerabilities unpatched. A mature policy combines controlled versions with timely updates.

5.2 Dependency Injection and Configuration Boundaries

In a full-stack system, deployment dependencies should not be confused with source dependencies. An application may use PostgreSQL, Redis, or an object store without placing those systems inside the same repository. Configuration should identify environment-specific endpoints and credentials through secure configuration mechanisms, not committed secrets.

Generated dependency directories, such as installed package trees, should normally not be committed when a manifest and lockfile can reproduce them. Exceptions may exist for specialized offline or embedded environments, but they should be explicit and documented.

6. Repository Structures for Full-Stack Systems

Repository structure influences discoverability, build performance, access control, release independence, and cognitive load. There is no universally superior arrangement. The correct design depends on coupling, ownership, deployment boundaries, and the need to coordinate changes.

A possible full-stack monorepo might be:

project/
├── apps/
│   ├── web/
│   ├── api/
│   └── worker/
├── packages/
│   ├── ui/
│   ├── domain-types/
│   └── config/
├── database/
│   ├── migrations/
│   └── seeds/
├── infrastructure/
├── docs/
├── package.json
└── lockfile

This layout separates deployable applications from reusable packages while keeping coordinated changes visible. It is a convention, not a requirement; the names and tools may differ across ecosystems.

6.1 Monorepos

A monorepo stores multiple applications or packages in one repository. It does not necessarily mean that all components share one version, build, or deployment. A monorepo may use independent package versions, a coordinated release, or a hybrid policy.

Advantages include:

  • atomic changes across frontend, backend, and shared types;
  • one location for common tooling and documentation;
  • easier code search and refactoring;
  • consistent policy enforcement;
  • simplified visibility of dependency relationships.

Challenges include:

  • larger checkout and indexing costs;
  • build and test orchestration complexity;
  • potential for unclear ownership;
  • accidental coupling through shared internals;
  • more complicated access control;
  • pressure to coordinate unrelated releases.

Workspaces and build-graph tools can improve performance through caching and affected-project analysis. However, tools do not remove the need for clear package boundaries and dependency rules.

6.2 Multirepos

A multirepo strategy places separate applications or libraries in separate repositories. It is often suitable when components have distinct owners, release cadences, security boundaries, or external consumers.

Advantages include:

  • independent permissions and lifecycle management;
  • smaller repositories and focused histories;
  • independent release pipelines;
  • clearer ownership for stable public libraries.

Costs include:

  • cross-repository change coordination;
  • duplicated tooling and policy;
  • more difficult atomic refactoring;
  • version lag between related components;
  • increased need for package registries and contract testing.

The choice should be based on actual boundaries, not on ideology. A tightly coupled product team may benefit from a monorepo, while independently governed services may be better separated.

CriterionMonorepoMultirepo
Atomic cross-component changeStrongRequires coordination
Independent permissionsMore difficultStrong
Repository sizeLargerSmaller per repository
Shared toolingCentralizedOften duplicated or packaged
Independent release cadencePossible but requires toolingNatural
Cross-component visibilityHighLower unless integrated

7. Submodules and Subtrees

Git submodules embed a reference to a specific commit in another repository. The parent repository records the external repository URL and commit identifier. A clone therefore requires an additional initialization and update process.

Typical commands are:

git submodule add https://example.org/shared.git vendor/shared
git submodule update --init --recursive

Submodules are useful when a component must retain an independent repository and the consuming project must pin an exact revision. They are less suitable when contributors frequently need to modify parent and child code together, because navigation, CI setup, and update workflows become more complex.

Git subtree imports another repository’s content into a directory in the parent repository. The imported files become ordinary files in the parent’s history, although subtree-aware commands can support synchronization.

Subtrees can be easier for consumers because a normal clone includes the content. Their disadvantages include duplicated history or more complex synchronization and a less explicit boundary between imported and local code.

TechniqueMain propertyAppropriate when
SubmodulePins external commit separatelyIndependent project must remain separate
SubtreeImports content into parentConsumers need a self-contained checkout
Package registryPublishes versioned artifactStable library/API consumption is desired

For reusable libraries, publishing a versioned package is often clearer than either submodules or subtrees. Those Git mechanisms are repository composition tools, not substitutes for dependency governance.

8. Git LFS and Large Files

Git stores snapshots efficiently for ordinary text source, but large binary files can make clones, fetches, and repository operations expensive. Git Large File Storage (LFS) replaces large files in Git history with small pointer files while storing the file content in an LFS server.

Typical setup:

git lfs install
git lfs track "*.psd"
git add .gitattributes design.psd
git commit -m "Track design assets with LFS"

The .gitattributes file is essential because it tells Git which paths use LFS. LFS does not make an unsuitable binary asset suitable, and it does not automatically remove large objects already committed to ordinary Git history. Historical cleanup may require specialized rewriting and coordination.

LFS is appropriate for selected large binaries such as design files, certain media assets, or machine-learning model files. It should not be used as a general solution for build artifacts, backups, secrets, or constantly changing databases. Teams must consider hosting quotas, backup policies, bandwidth, access permissions, and whether a package or object-storage system is more appropriate.

9. Evaluating a Repository Design

A structured evaluation can prevent repository decisions based only on personal preference. Ask the following questions:

Ownership and boundaries

Who reviews each component? Do the proposed boundaries match team ownership and security responsibilities? A repository that combines unrelated confidential and public assets may create governance problems.

Change coupling

How often must one change update several components atomically? High coupling favors a monorepo or a well-integrated workspace. Low coupling favors independent repositories or published packages.

Release independence

Do components have different release schedules, compatibility contracts, or deployment targets? If so, independent packages or repositories may reduce coordination cost.

Build and test behavior

Can continuous integration test only affected components? Can it reproduce production builds? A large repository without caching, dependency graphs, or selective testing may become impractical.

Deployment boundary

A deployable unit need not equal a repository, but the relationship should be explicit. For example, a web application, API, and worker may be released together from a monorepo or independently from separate repositories. Database migrations require special care because deployment ordering and backward compatibility matter.

Governance and security

Can the organization enforce review, secret scanning, license policy, branch protection, artifact retention, and audit requirements? Repository design should support these controls rather than conceal them.

A practical decision framework is to score candidate structures against these criteria and document trade-offs. The goal is not to maximize repository count or minimize it; it is to reduce total delivery risk and coordination cost.

10. Applied Case: Northstar Commerce

The following is an illustrative case.

Background

Northstar Commerce operates a web storefront, an API, a background order worker, a shared TypeScript domain model, database migrations, and infrastructure scripts. Initially, each component was in a separate repository. The frontend and API frequently changed together, but the shared model was copied manually between repositories.

Problem

Copying types caused drift. A field added to the API could be absent from the frontend, while release coordination required several manually ordered pull requests. At the same time, infrastructure credentials and deployment configuration needed stricter access control than application source.

Analysis

The storefront, API, worker, and shared model had high change coupling and benefited from atomic review. Infrastructure had different permissions and a separate operational ownership model. Large design files were also slowing ordinary Git operations.

Approach

Northstar adopted a monorepo for web, api, worker, and shared packages. It used workspace dependency management, a committed lockfile, affected-project CI, and contract tests. The infrastructure code remained in a separate repository because of access and lifecycle requirements. The team published selected shared packages for external consumers rather than exposing internal source paths.

Releases were tagged with annotated SemVer tags. The product used coordinated versions for deployable applications, while independently consumed libraries used their own versions. Release notes documented database migration order, rollback constraints, and API changes. Design files were moved to LFS after reviewing storage quotas and access policies.

Outcome

Cross-component changes became atomic and easier to review. CI time was controlled through caching and affected-project testing. The separation of infrastructure reduced privilege exposure, while package publication established a stable contract for external consumers.

Learning Points

The solution was hybrid rather than doctrinaire. Repository boundaries followed ownership, security, and release behavior. A monorepo solved coordination problems, but it required tooling and explicit package boundaries. Versioning and release notes converted repository history into an understandable product interface.

11. Challenges, Limitations, and Governance

Version numbers can create false confidence. A project may follow SemVer mechanically while failing to document behavioral compatibility, operational risk, or migration consequences. Teams should treat versioning as communication supported by tests and policy.

Dependency automation can also introduce risk. Automatically merging updates without testing, license review, or runtime validation may create outages. Conversely, rejecting all automated updates increases technical debt. Appropriate controls include staged updates, compatibility tests, approval rules, and rollback plans.

Repository history may contain secrets, personal information, proprietary code, or unlawful material. Removing a secret from the latest commit does not necessarily remove it from all clones or server backups. Incident response should revoke and rotate exposed credentials first, then assess history rewriting and notification obligations.

AI-assisted coding and release-note generation can improve productivity, but generated code and summaries require human review. AI tools may reproduce vulnerable dependencies, misclassify breaking changes, or disclose repository content to external services. Organizations should establish policies for data handling, attribution, review, and tool access.

Contemporary teams increasingly use supply-chain frameworks such as software bills of materials, provenance attestations, signed commits or tags, and isolated build environments. These practices help answer what was built, from which source, with which dependencies, and by which process. They complement—not replace—secure development and operational monitoring.

12. Best-Practice Release Checklist

Before publishing a release, confirm that:

  • the version follows the project’s documented compatibility policy;
  • public API and migration effects have been reviewed;
  • dependencies and lockfiles are consistent;
  • tests, static analysis, security checks, and builds have passed;
  • artifacts can be traced to a precise commit;
  • an annotated, protected tag has been created;
  • release notes explain user and operator impact;
  • rollback and database migration constraints are understood;
  • secrets and generated artifacts are not accidentally committed;
  • ownership, approvals, and audit records are available.

Chapter Summary

Releases transform repository history into a tested, identified, and communicated product state. Semantic versioning communicates compatibility, but only when the public contract is defined and changes are evaluated carefully. Annotated tags provide durable release references and can support signing and verification. Changelogs record project history, whereas release notes explain the significance of a particular release.

Dependencies form a software supply chain. Manifests, lockfiles, vulnerability review, license analysis, and update policies are necessary for reproducible and maintainable delivery. Monorepos support atomic cross-component changes and shared tooling, while multirepos support independent ownership, permissions, and release lifecycles. Submodules and subtrees solve particular repository-composition problems; package registries are often preferable for stable library distribution. Git LFS handles selected large binary assets but is not a replacement for artifact repositories or object storage.

The best repository structure is determined by coupling, ownership, security, build behavior, deployment boundaries, and release independence. Professional teams document these trade-offs, protect release history, and combine automation with human judgment.

Key Terms

Semantic versioning: A versioning convention using major, minor, and patch components to communicate compatibility.

Major version: The SemVer component incremented for an incompatible public change.

Minor version: The SemVer component incremented for backward-compatible functionality.

Patch version: The SemVer component incremented for backward-compatible fixes.

Annotated tag: A Git tag object containing metadata such as tagger, date, message, and target.

Release: A tested and communicated distribution of a particular source state.

Artifact: A built output intended for deployment or distribution.

Changelog: A continuing record of notable changes across project versions.

Release note: Communication describing the impact and operational details of one release.

Direct dependency: A dependency explicitly declared by a project.

Transitive dependency: A dependency required indirectly through another dependency.

Lockfile: A file recording resolved dependency versions and integrity details.

Monorepo: One repository containing multiple applications or packages.

Multirepo: A strategy using separate repositories for different components.

Submodule: A Git repository referenced at a specific commit inside another repository.

Subtree: Repository content imported into a directory of another repository.

Git LFS: A Git extension that stores large file content outside normal Git objects using pointer files.

Artifact provenance: Evidence connecting a built artifact to its source, dependencies, and build process.

Review Questions

5 Short Answer Questions

  1. What is the difference between a Git tag and a release?
  2. State the three primary Semantic Versioning components and their usual meanings.
  3. Why is a lockfile useful for an application?
  4. What problem does Git LFS address?
  5. Give one advantage and one disadvantage of a monorepo.

5 Long Answer Questions

  1. Explain how a team should decide whether a change requires a major, minor, or patch version.
  2. Compare annotated tags with lightweight tags and explain why annotated tags are generally preferred for formal releases.
  3. Discuss the benefits and risks of monorepo and multirepo strategies for a full-stack product.
  4. Explain how direct and transitive dependencies affect software supply-chain risk.
  5. Compare submodules, subtrees, and published packages as methods for sharing code.

3 Analytical / Application Questions

  1. A full-stack team frequently changes its frontend, API, and shared types together, but its infrastructure team requires separate access control. Propose a repository structure and justify it using coupling, ownership, and security criteria.
  2. A library removes a public function, adds an optional feature, and fixes an internal defect in the same development cycle. Explain how each change should be communicated and versioned, and identify what additional evidence should be reviewed.
  3. A release passes unit tests but includes a newly vulnerable transitive dependency and an undocumented database migration. Should it be published? Analyze the dependency, release-governance, and rollback issues before reaching a decision.

Multiple Choice Questions with Answers

Question 1

Which version best represents a backward-compatible bug fix when the current release is 2.4.1?

A. 3.0.0

B. 2.5.0

C. 2.4.2

D. 2.4.1-beta.1

Correct Answer: C. 2.4.2

Question 2

What is the principal advantage of an annotated Git tag for a formal release?

A. It automatically compiles the project

B. It stores release metadata and can be signed

C. It permanently prevents all branch changes

D. It replaces the project lockfile

Correct Answer: B. It stores release metadata and can be signed

Question 3

Which file primarily records exact resolved dependency versions for reproducible installation?

A. A lockfile

B. A changelog

C. A README title

D. A Git hook directory

Correct Answer: A. A lockfile

Question 4

Which situation most strongly favors a monorepo?

A. Components have unrelated owners and strict isolation requirements

B. A library must be distributed to many external consumers

C. Frontend, API, and shared types frequently require atomic changes

D. Every component has a completely independent release lifecycle

Correct Answer: C. Frontend, API, and shared types frequently require atomic changes

Question 5

What does a Git submodule record in the parent repository?

A. Every file from the child repository as ordinary parent files

B. A reference to a specific commit in another repository

C. Only the child repository’s uncommitted changes

D. A compiled binary of the child repository

Correct Answer: B. A reference to a specific commit in another repository

Question 6

Which statement about Git LFS is correct?

A. It stores large file content through pointer files and an LFS server

B. It automatically removes all large objects from existing history

C. It is designed primarily for database backups and secrets

D. It eliminates the need for storage quotas

Correct Answer: A. It stores large file content through pointer files and an LFS server

Question 7

What is the main purpose of a release note?

A. To replace all automated tests

B. To explain the impact, changes, and operational information for a release

C. To store dependency binaries

D. To define Git’s internal object format

Correct Answer: B. To explain the impact, changes, and operational information for a release

Question 8

A public function is removed from a library. Under ordinary SemVer rules, which increment is generally appropriate?

A. PATCH

B. MINOR

C. MAJOR

D. Build metadata only

Correct Answer: C. MAJOR

Question 9

Which repository strategy naturally supports independent permissions for separate components?

A. Multirepo

B. A single unpartitioned directory

C. A single annotated tag

D. Git LFS

Correct Answer: A. Multirepo

Question 10

Why should a published release tag generally not be silently moved?

A. Moving it would change the historical meaning of the release reference

B. Git cannot store tags on remote repositories

C. Tags can contain only binary files

D. Moving it automatically deletes every branch

Correct Answer: A. Moving it would change the historical meaning of the release reference

Git, GitHub & Version Control
Chapter 9 of 14