Skip to content

FLX-ENG-RFC-009 — Repository Consolidation & Cutover Plan

Field Value
RFC ID FLX-ENG-RFC-009
Status Active — transition in progress
Author Arun Singh, Senior Distinguished Engineer / Architect (Consulting)
Reviewers Raja Choudhary (Founder), Rahul Bahuguna (Eng Lead), Tushar Garg
Scope Migration of all active development from Distribution-Management-Server-Layered (legacy) to dms_ci_cd_test (canonical); sync agent operation; branch standardisation; decommission sequence
Supersedes Ad-hoc dual-repo workflow; non-RFC-compliant branch names
Related RFC FLX-ENG-RFC-001 §3.2.2 (branching model), Issue #54 (dev branch retirement)
GitHub Workflow .github/workflows/repo-sync.yml
Sync Script scripts/sync/repo_sync.py

TL;DR — The decision

A single automated sync agent mirrors every developer commit from the legacy repo into the canonical repo every 15 minutes, normalising branch names to RFC §3.2.2 standard, enforcing DCO sign-off, and resolving trivial conflicts automatically. When all developers confirm they have switched to the canonical repo, a single --cutover trigger archives the legacy repo, retiring it permanently.

Developers do not need to change anything during the transition period.
After cutover, one git remote set-url command is all that's needed.


1. Problem

Two GitHub repositories exist for the same product:

Repository Role State
Distribution-Management-Server-Layered Legacy — where developers currently commit Active, 6 branches, non-RFC names
dms_ci_cd_test Canonical — has new CI gates, RFC docs, DORA setup Active, only main branch

Running two active repos creates:

  • Split history — defects fixed in one repo silently absent from the other
  • CI gate bypass — developers on the legacy repo miss all 10 quality gates (Epic #6)
  • DORA metric distortion — CodePulse measures only the canonical repo; legacy PRs don't count
  • Branch naming debt — legacy branches are 21-bagging-flow, not feature/21-bagging-flow; RFC §3.2.2 is violated before anyone even writes a line of new code
  • DCO gaps — Windows domain author RAHUL-BAHUGUNA\RAHUL does not produce a valid Signed-off-by trailer

2. Design Principles

  1. Zero developer disruption during transition. Developers keep committing to the legacy repo. The sync agent handles the rest.
  2. Every commit reaches canonical. No work is lost, dropped, or silently skipped. SQLite state and patch-id deduplication guarantee exactly-once delivery.
  3. Branch names are normalised on import, not after. Legacy feature branches arrive in canonical already named feature/<id>-<slug>.
  4. Long-lived development branches are retired, not migrated. dev and prod are archived as timestamped tags and their unique commits are merged to main first.
  5. CI/docs files always take target precedence. The 10 quality gates, RFC docs, and MkDocs config committed to canonical are never overwritten by a legacy push.
  6. Cutover is a single command, not a manual procedure. All complexity is front-loaded into the sync agent; cutover is atomic and reversible (the archive tag preserves all history).

3. Repositories

Property Legacy (source) Canonical (target)
URL github.com/Flexli-Technologies/Distribution-Management-Server-Layered github.com/Flexli-Technologies/dms_ci_cd_test
Branches main, dev, prod, 21-bagging-flow, 22-sorterprofileendpoint-dlink-archivalworker, 27-meesho-server-comm-testing, 29-manual-sortation-endpoints-and-sorter-profile-endpoint main
CI gates None 10 automated gates (Epic #6, RFC-005)
RFC docs None docs/rfcs/FLX-ENG-RFC-001 through RFC-009
DORA tracking None (CodePulse not configured) Active (CodePulse, EPIC-2)
Status after cutover Archived (read-only) Canonical — all development

4. Branch Mapping — Legacy → RFC Standard

RFC FLX-ENG-RFC-001 §3.2.2 and GitHub Issue #54 define the allowed branch taxonomy:

main                      sole long-lived branch
feature/<id>-<slug>       short-lived, ≤ 1 sprint
release/<major.minor>     release train branch
hotfix/<id>               emergency fix

Current legacy branches

Legacy name RFC target name Disposition Reason
main main Mirror Already correct
dev (none) Retire Long-lived dev branch banned (RFC §3.2.2). Unique commits merged to main first, then archive/dev-YYYYMMDD tag created.
prod (none) Archive Production tracking branch; archive/prod-YYYYMMDD tag preserves history. Not needed in trunk-based model.
21-bagging-flow feature/21-bagging-flow Sync Issue-number prefix extracted; feature/ added
22-sorterprofileendpoint-dlink-archivalworker feature/22-sorterprofile-dlink-archival Sync Slug truncated to 40 chars
27-meesho-server-comm-testing feature/27-meesho-server-comm-testing Sync RFC-compliant after adding feature/
29-manual-sortation-endpoints-and-sorter-profile-endpoint feature/29-manual-sortation-endpoints Sync Slug truncated

Mapping algorithm (deterministic)

input branch name
       ├─ == "main"             → target: main,   SKIP (mirror)
       ├─ in {dev, develop}     → RETIRE (archive tag, merge-to-main)
       ├─ in {prod, production} → ARCHIVE (tag only)
       ├─ already RFC-compliant → SKIP (mirror as-is)
       ├─ starts with feature/  → re-slug inner part
       ├─ starts with release/  → normalise version string
       ├─ starts with hotfix/   → normalise issue ID
       ├─ matches \d+-.*        → prefix with feature/
       └─ anything else         → feature/0-<slugified> (fallback, warns)

5. Sync Agent Architecture

┌─────────────────────────────────────────────────────────────────┐
│  GitHub Actions  (*/15 * * * *  or workflow_dispatch)           │
│                                                                   │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │  SyncEngine.sync_once()                                    │  │
│  │                                                             │  │
│  │  1. git fetch --all --prune  (source)                      │  │
│  │  2. git fetch --all --prune  (target)                      │  │
│  │  3. for each source branch:                                 │  │
│  │       a. BranchMapper → disposition + target name          │  │
│  │       b. if RETIRE  → _retire_branch()                     │  │
│  │       c. if ARCHIVE → _archive_branch()                    │  │
│  │       d. if SYNC    → _mirror_branch()                     │  │
│  │                                                             │  │
│  │  _mirror_branch():                                         │  │
│  │    • check tip SHA vs last_synced in SQLite                │  │
│  │    • collect commits ahead: git log <last>..<tip>          │  │
│  │    • deduplicate via git patch-id --stable                 │  │
│  │    • for each new commit:                                  │  │
│  │        – resolve author (Windows domain → real identity)   │  │
│  │        – git cherry-pick onto target branch                │  │
│  │        – on conflict: try auto-resolve                     │  │
│  │          · whitespace → keep target                        │  │
│  │          · .github/ docs/ → keep target                    │  │
│  │          · else → conflict branch + GH issue               │  │
│  │        – enforce DCO (Signed-off-by)                       │  │
│  │    • git push origin <target-branch>                       │  │
│  │    • update SQLite: tip_sha, last_synced                   │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                   │
│  SQLite  /tmp/dms-sync/sync_state.db  (cached between runs)      │
└─────────────────────────────────────────────────────────────────┘

Memory and compute profile

Operation Memory bound Why
git fetch O(delta) Only transmits new objects
Commit traversal O(500) patch-ids max Capped to bound dedup cost
Cherry-pick O(diff size) Per-commit, not whole branch
SQLite < 1 MB typical Keyed by SHA (fixed-size rows)
Total < 50 MB RSS Suitable for GitHub Actions free tier

6. DCO — Developer Certificate of Origin

Every commit reaching the canonical repo must carry:

Signed-off-by: Developer Name <developer@email.com>

This certifies the developer has the right to contribute the code (see DCO 1.1).

Enforcement in the sync agent

  1. After cherry-picking each commit onto the target branch, the agent checks the commit message for a Signed-off-by: trailer.
  2. If absent, it amends HEAD in-place using git commit --amend with the resolved author identity.
  3. The original commit SHA is recorded in SQLite (synced_commits.sha) so future runs do not re-process it.

Developer responsibility

Going forward, every PR to either repo (during transition) or to canonical (after cutover) must include a sign-off. Two ways to do this:

# Option A: Always sign-off automatically
git config --global commit.gpgsign false
git config --global format.signoff true

# Option B: Per commit
git commit -s -m "feat: your commit message"

# Option C: Amend last commit if you forgot
git commit --amend -s --no-edit

7. Author Identity Map

Legacy commits from Rahul use a Windows domain account name. The sync agent remaps these before writing to canonical:

Git config value (legacy) Canonical identity Email
RAHUL-BAHUGUNA\RAHUL Rahul Bahuguna rahul@flexli.in
RAHUL-BAHUGUNA/RAHUL Rahul Bahuguna rahul@flexli.in
Tushar Garg Tushar Garg tushar@flexli.in

Action for Rahul: Fix your local Git identity permanently so new commits on any repo use the correct name:

git config --global user.name  "Rahul Bahuguna"
git config --global user.email "rahul@flexli.in"

Verify: git config --global --list | grep user


8. Conflict Resolution Policy

Auto-resolved (no human needed)

Conflict type Resolution Rationale
Whitespace-only diff Keep target Formatting is enforced by dotnet format gate
.github/workflows/ conflict Keep target CI gates must not be overwritten by legacy
docs/ and docs/rfcs/ conflict Keep target RFC docs are canonical in the new repo
mkdocs.yml, Makefile, requirements-docs.txt Keep target Docs infra set up in canonical
File deleted on source, exists on target Keep target Assume target added something new

Escalated to human (conflict branch + GitHub issue)

Any conflict in application source code (src/, DistributionServerUnitTest/, docker/) that cannot be trivially resolved triggers:

  1. A conflict/sync-<branch>-<sha> branch is pushed to canonical with the conflicting changes
  2. A GitHub issue is opened in canonical labelled P1-high and sync-conflict
  3. The issue body contains the exact git checkout and git merge commands to resolve it
  4. The sync agent stops processing further commits on that branch until the issue is closed

Resolution checklist for the assigned developer

# 1. Fetch and checkout the conflict branch
git fetch origin
git checkout conflict/sync-feature-21-bagging-flow-abc12345

# 2. Merge into the target branch
git checkout feature/21-bagging-flow
git merge conflict/sync-feature-21-bagging-flow-abc12345

# 3. Resolve conflicts in your editor, then:
git add <resolved-files>
git commit -s -m "fix: resolve sync conflict on feature/21-bagging-flow

Closes #<issue-number>"

# 4. Push and close the issue
git push origin feature/21-bagging-flow
gh issue close <issue-number>

9. Step-by-Step Process

Phase 1 — Pre-transition (complete these before enabling the workflow)

Step 1.1 — Create the sync secret

In dms_ci_cd_test → Settings → Secrets and variables → Actions → New repository secret:

Name:  SYNC_GH_TOKEN
Value: <GitHub PAT>

The PAT needs these scopes: - repo — read/write both repos - workflow — update GitHub Actions workflows - admin:org — archive the source repo at cutover (or ask Raja to do this step)

Step 1.2 — Fix developer Git identities

Each developer runs:

# Rahul
git config --global user.name  "Rahul Bahuguna"
git config --global user.email "rahul@flexli.in"

# Tushar
git config --global user.name  "Tushar Garg"
git config --global user.email "tushar@flexli.in"

Step 1.3 — Enable the GitHub Actions workflow

dms_ci_cd_test → Actions → DMS Repository Sync → Enable workflow

Step 1.4 — Verify first sync run

dms_ci_cd_test → Actions → DMS Repository Sync → latest run

Check: - [ ] All legacy branches appear in canonical with feature/ prefix - [ ] dev branch is tagged archive/dev-<date> and deleted from source - [ ] prod branch is tagged archive/prod-<date> and deleted from source - [ ] Each feature branch's commits are in canonical with correct author names - [ ] No commits are missing Signed-off-by in canonical


Phase 2 — Steady state (developers work normally, sync runs every 15 min)

Developer workflow during transition (no changes needed):

# Developers continue working in the legacy repo as usual
cd Distribution-Management-Server-Layered/

# Create feature branches (the sync agent will prefix feature/ if missing)
git checkout -b 42-new-endpoint
# ... work ...
git commit -s -m "feat: add new sorting endpoint (#42)"
git push origin 42-new-endpoint
# Open PR on legacy repo

# Every 15 minutes, the sync agent picks up new commits
# and pushes feature/42-new-endpoint to dms_ci_cd_test

Monitoring syncs:

# Check sync logs (CLI)
gh run list --repo Flexli-Technologies/dms_ci_cd_test --workflow=repo-sync.yml

# View specific run log
gh run view <run-id> --repo Flexli-Technologies/dms_ci_cd_test --log

# Check for conflict issues
gh issue list --repo Flexli-Technologies/dms_ci_cd_test --label P1-high

Phase 3 — Cutover (when all developers are ready to switch)

Step 3.1 — Confirm cutover readiness checklist

  • All open PRs on the legacy repo are merged or closed
  • No commits pushed to legacy in the last 24 hours (sync has caught up)
  • All developers have confirmed they have read this document
  • All conflict issues on canonical are resolved
  • Raja has approved the cutover date

Step 3.2 — Trigger cutover

dms_ci_cd_test → Actions → DMS Repository Sync → Run workflow → mode: cutover

Or via CLI:

GH_TOKEN=<token> python scripts/sync/repo_sync.py --once --cutover

What the cutover run does (automated, ~5 minutes):

  1. Final sync of all remaining source branches
  2. Archive every remaining source branch as archive/<branch>-final-<date> tag
  3. Add decommission notice to source README.md with redirect URL
  4. Push the notice commit to source main
  5. Call GitHub API to archive the source repo (sets it read-only)
  6. Update canonical repo description and homepage URL

Step 3.3 — Every developer migrates their local clone

# One-time command — takes 10 seconds
git remote set-url origin https://github.com/Flexli-Technologies/dms_ci_cd_test.git
git fetch origin
git branch --set-upstream-to=origin/main main

# Verify
git remote -v
# origin  https://github.com/Flexli-Technologies/dms_ci_cd_test.git (fetch)
# origin  https://github.com/Flexli-Technologies/dms_ci_cd_test.git (push)

Step 3.4 — Verify post-cutover state

  • Distribution-Management-Server-Layered shows "Archived" badge on GitHub
  • Source README shows the decommission notice with link to canonical
  • git push to source fails with "Repository is archived"
  • All canonical branches visible: main, feature/21-bagging-flow, etc.
  • CI gates run on first PR opened in canonical

Phase 4 — Post-cutover (all work in canonical)

New developer workflow in canonical:

# Create feature branch (RFC §3.2.2 standard names required from day 1)
git checkout -b feature/53-net8-port main
# ... work ...
git commit -s -m "feat: port DMS to .NET 8 LTS (#53)"
git push origin feature/53-net8-port

# Open PR → all 10 CI gates run automatically
gh pr create --title "feat: port DMS to .NET 8 LTS" \
  --body "Closes #53\n\n## Summary\n..." \
  --base main

Branch naming quick reference (issue #54):

# Feature work
git checkout -b feature/<github-issue-id>-<2-5-word-slug>
# Example: feature/53-net8-port, feature/60-openfeature-sdk

# Release
git checkout -b release/1.2   # from main, after sprint freeze

# Hotfix
git checkout -b hotfix/99     # from main, merged back to main + release/*

10. Decommission Artifacts

After cutover, these artifacts provide permanent proof of the migration:

Artifact Location Contains
Archive tags Source repo (archived) archive/dev-YYYYMMDD, archive/prod-YYYYMMDD, archive/*-final-YYYYMMDD
Sync state DB GitHub Actions cache / download artifact Full audit of every commit synced: SHA, branch, timestamp
Sync run logs GitHub Actions → Artifacts (30-day retention) Per-run output with branch results
Conflict issues dms_ci_cd_test → Issues (labelled sync-conflict) Any conflicts that required manual resolution
Decommission notice Source README.md (archived) Redirect link to canonical

11. Rollback Plan

The sync is non-destructive to the legacy repo (it only pushes to canonical). If the sync agent causes problems:

# Stop the scheduler
# dms_ci_cd_test → Actions → DMS Repository Sync → Disable workflow

# If cutover was triggered but needs to be reversed
gh api --method PATCH repos/Flexli-Technologies/Distribution-Management-Server-Layered \
  -f archived=false
# (requires admin:org scope)

Legacy branches retired during sync can be restored from their archive tags:

git checkout -b dev archive/dev-YYYYMMDD
git push origin dev

12. Success Criteria

  • All commits from Distribution-Management-Server-Layered are present in dms_ci_cd_test with original author attribution
  • Every commit in canonical carries Signed-off-by (verifiable via git log --grep="Signed-off-by")
  • All feature branches have RFC-compliant names in canonical
  • dev and prod are retired (archive tags exist, branches deleted)
  • Source repo is archived on GitHub (read-only)
  • First PR in canonical runs all 10 CI gates successfully
  • No developer commit is missing from canonical (diff: git log canonical/main ^legacy/main is empty)
  • CodePulse DORA data collection active in canonical (EPIC-2 US-2.1)