How I Synchronized Git Branches and Committed Test Improvements: A Step-by-Step Guide

How I Synchronized Git Branches and Committed Test Improvements: A Step-by-Step Guide

Introduction

In this guide, I'll walk through the process of synchronizing Git branches and committing test improvements in a development workflow. This real-world scenario involved syncing main and develop branches while preserving important test migration work.

What makes this particularly valuable is that it demonstrates how to handle a complex situation that many developers face: managing ongoing work while needing to synchronize branches and organize changes into meaningful commits. This scenario occurred during a critical test framework migration from Jest to Vitest, along with fixing production bugs in the notification system.

Initial Situation

I started with uncommitted changes on the develop branch and needed to:

  1. Synchronize main and develop branches
  2. Commit test framework migration from Jest to Vitest
  3. Fix critical bugs in the notification system

This situation is common in development environments where multiple features and fixes are being developed simultaneously, and proper Git hygiene is essential for collaboration.

Step 1: Assess Current State

First, I checked the repository status and branch structure to understand the complete picture:

$ git status
$ git branch -a
$ git remote -v

Output:

On branch develop
Your branch is up to date with 'origin/develop'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   erp-frontend/src/components/common/__tests__/ConnectionStatus.test.tsx
        modified:   erp-frontend/src/components/common/__tests__/NotificationManager.test.tsx
        [... more files ...]

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        erp-frontend/src/__mocks__/
        erp-frontend/src/utils/idGenerator.ts

This output revealed I had both modified files (test migrations) and new files (bug fixes and mocks) that needed to be properly organized and committed.

Step 2: Preserve Work in Progress

Before switching branches, I stashed my changes to ensure nothing would be lost:

$ git stash push -m "WIP: Test file modifications and new mocks"

Output:

Saved working directory and index state On develop: WIP: Test file modifications and new mocks

The descriptive stash message is crucial here. It helps identify the stashed work later, especially in projects where multiple stashes might accumulate. This practice has saved me countless times when I need to quickly switch contexts.

Step 3: Analyze Branch Relationships

I examined how the branches had diverged to understand what needed to be synchronized:

$ git log --oneline --left-right --graph main...develop

Output:

> b3e1568 (HEAD -> develop, origin/develop) fix: resolve Docker build failure in CI/CD pipeline

This showed develop was one commit ahead of main. The --left-right option clearly indicates which commits belong to which branch, making it easy to understand the divergence.

Step 4: Synchronize Main with Develop

I switched to main and fast-forwarded it to match develop:

$ git checkout main
$ git merge --ff-only develop

Output:

Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Updating 477cefc..b3e1568
Fast-forward
 erp-frontend/Dockerfile | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

Using --ff-only ensures a clean, linear history when possible. If the branches had diverged in incompatible ways, this command would fail safely rather than creating a merge commit.

Step 5: Push Updated Main

$ git push origin main

Output:

To github.com:username/repo.git
   477cefc..b3e1568  main -> main

This ensures the remote repository stays synchronized with local changes, allowing team members to pull the latest updates.

Step 6: Restore Work and Commit Changes

I returned to develop and restored my work:

$ git checkout develop
$ git stash pop

The stashed changes were now back in my working directory, ready to be organized into logical commits.

Step 7: Create Logical Commits

I organized my changes into meaningful commits, each addressing a specific aspect of the work. This is where Git usage really shines — creating a clear history that tells the story of the changes.

Commit 1: Critical Bug Fix

$ git add erp-frontend/src/utils/idGenerator.ts erp-frontend/src/store/slices/uiSlice.ts
$ git commit -m "fix: resolve notification ID duplication issue with robust ID generator

- Replace Date.now() with proper ID generator in uiSlice
- Add counter-based unique ID generation to prevent duplicates
- Fix issue where multiple notifications created in same millisecond shared IDs"

This commit addresses a production bug where notifications could share IDs if created rapidly. The commit message follows conventional commit standards and clearly explains the problem and solution.

Commit 2: Test Infrastructure

$ git add erp-frontend/src/__mocks__/@mui/material/Snackbar.tsx
$ git commit -m "test: add Material-UI Snackbar mock for reliable testing

- Create mock that renders inline instead of using React Portals
- Fixes test environment issues with Portal-based components"

Separating test infrastructure changes makes it easy to understand why certain mocks were necessary and helps future developers who might encounter similar issues.

Commit 3: Test Migration

$ git add erp-frontend/src/setupTests.ts erp-frontend/vitest.config.ts \
  erp-frontend/src/test-utils/test-utils.tsx erp-frontend/src/test-utils/mock-data.ts
$ git commit -m "test: migrate from Jest to Vitest and update test infrastructure

- Update setupTests.ts to use Vitest syntax and mocks
- Configure Vitest with proper jsdom environment and timeouts
- Update test-utils to handle Redux serialization for Date objects"

This commit groups all configuration changes related to the test framework migration, making it a self-contained unit of work.

Commit 4: Test Updates

$ git add erp-frontend/src/components/common/__tests__/*.test.tsx \
  erp-frontend/src/pages/production/__tests__/*.test.tsx \
  erp-frontend/src/store/slices/__tests__/*.test.ts
$ git commit -m "test: update test files to use Vitest and fix test reliability

- Convert all test files from Jest to Vitest syntax
- Fix async handling and assertions
- Update mocking strategies"

By committing the actual test file updates separately, it's easy to review the syntax changes and understand the migration's scope.

Step 8: Push All Changes

$ git push origin develop

Output:

Enumerating objects: 66, done.
Counting objects: 100% (66/66), done.
Delta compression using up to 8 threads
Compressing objects: 100% (40/40), done.
Writing objects: 100% (43/43), 12.04 KiB | 4.01 MiB/s, done.
Total 43 (delta 30), reused 0 (delta 0), pack-reused 0
To github.com:username/repo.git
   b3e1568..ae4b54e  develop -> develop

The detailed output shows Git efficiently packing and sending only the necessary objects to the remote repository.

Step 9: Final Synchronization

After committing all changes, I synced main with the updated develop to ensure both branches remained aligned:

$ git checkout main
$ git merge --ff-only develop
$ git push origin main

This maintains the principle that main should always be deployable and up-to-date with completed work from develop.

Step 10: Verify Synchronization

$ git log --oneline --all --graph --decorate -5

Output:

* ae4b54e (HEAD -> main, origin/main, origin/develop, develop) test: update test files to use Vitest
* 09960e4 test: migrate from Jest to Vitest and update test infrastructure
* 27f7a40 test: add Material-UI Snackbar mock for reliable testing
* 7611b2e fix: resolve notification ID duplication issue with robust ID generator
* b3e1568 fix: resolve Docker build failure in CI/CD pipeline

The aligned branch pointers confirm successful synchronization, and the clean, linear history tells a clear story of the changes made.

Key Takeaways

  1. Always stash work before switching branches to avoid losing changes
  2. Use descriptive commit messages that explain what and why
  3. Create logical commits that group related changes together
  4. Verify branch relationships before merging to avoid conflicts
  5. Use fast-forward merges when possible to maintain linear history
  6. Push regularly to backup work and enable collaboration

Benefits of This Approach

  • Clean commit history: Each commit has a single, clear purpose
  • Easy rollback: Problems can be isolated to specific commits
  • Better collaboration: Team members can understand changes easily
  • Git workflow: Demonstrates attention to detail and best practices

Advanced Tips

Beyond the basics shown here, consider these additional practices:

  • Use git stash list to manage multiple stashes effectively
  • Consider git rebase -i for cleaning up commits before pushing
  • Set up Git aliases for commonly used commands
  • Use git log --grep to search commit history by message content
  • Implement Git hooks for automated checks before commits

Conclusion

This systematic approach to branch synchronization and commit management ensures code quality, maintains a clean repository history, and facilitates team collaboration. By following these steps, I successfully migrated a test framework while keeping the main and develop branches in sync.

The key to Git usage isn't just knowing the commands - it's understanding when and why to use them to create a maintainable, understandable project history. This workflow demonstrates not just technical capability, but also the thoughtfulness and attention to detail that characterizes senior-level development practices.


This guide demonstrates real-world Git workflow management, showcasing skills in version control, code organization, and development practices that are essential in modern software development teams.

Keywords: Git workflow, branch synchronization, test migration, version control best practices, and development.


If you enjoyed this article, you can also find it published on LinkedIn and Medium.