Greenstand / Greenstand/treetracker-android

Branching strategy, release tagging, and upstream cleanup

Open
#1,289 1 comment 0 reactions 0 assignees View on GitHub
2.3 chore Documentation not ready for dev triage
Dominant language
Kotlin
Stars
101
Forks
116
PR merge metrics
No merged PRs in 30d

Description

Facts verified against `upstream` (Greenstand) on 2026-05-17. The team workflow is the standard GitHub fork-and-PR flow: fork → personal branch on the fork → PR to upstream. Long-lived branches on upstream should only be release lines and (rarely) explicit team-wide collaboration branches.

## TL;DR

The release branch convention is already right: `releases/2.2.1` exists, is correctly named, versionCode 202, versionName 2.2.1. The actual problems are:

1. **Repo setting `delete_branch_on_merge` is off.** Squash-merged PR sources stay forever as stale upstream branches.
2. **No branch protection** on `master` or `releases/2.2.1`. Feature branches end up pushed directly to upstream instead of staying on forks.
3. **Zero git tags for any 2.x release.** No anchor for "what commit shipped as 2.2.0" or "what commit is live as 2.1.4."
4. **136 of 203 upstream branches are >2 years old.** Mostly long-dead feature branches that were never deleted.
5. **`releases/2.2.1` is 3 ahead / 7 behind master**; no documented decision on which master commits forward-port.
6. **The branching/tag question was raised on #1187 (2026-03-11);** the subsequent thread focused on environment setup. This issue resumes that thread.

---

## Observed state

### Tags

13 tags total, newest from May 2019. Zero tags for any 2.x release. The commits submitted to Play Console for 2.0, 2.1, 2.1.1, 2.1.3, 2.1.4, 2.2.0 are not pointed at by anything in the git graph.

### Repo settings

- `delete_branch_on_merge: false`
- No branch protection configured on `master` or `releases/2.2.1` (verified via the GitHub branch protection API)

### `releases/2.2.1` is canonical; the look-alike branches are PR sources or WIP

Four upstream branches stamped versionCode 202 / 2.2.1. Only one is a release line; the others are PR sources or WIP that would normally live on contributor forks:

| Branch | Status | What it really is |
|---|---|---|
| `releases/2.2.1` | canonical | the 2.2.1 release line |
| `feat/bdd-for-2.2.1` | merged-and-stale | source of PR **#1278** (BDD), squashed into `releases/2.2.1` as `29d7c0fa`. Would have been auto-deleted with the right repo setting. |
| `feat/bdd-test-releases-2.2.1` | abandoned WIP | 1 unique commit, no open PR — apparent earlier BDD attempt |
| `jonathanmuller/merge-release-2.2.1` | active | head of open PR **#1260** "merge release changes into master" |

### Same pattern on the 2.1.3 side

| Branch | Status |
|---|---|
| `release-2.1.3_candidate-2` | active — head of open PR **#1193** "DO NOT MERGE" (deliberate testing branch) |
| `release-2.1.3_candidate-1` | superseded, no open PR |
| `feat/rollback-release-2.1.3` | unclear — PR #1277 (BDD) was merged onto this; no current PR |
| `feat/bdd-for-2.1.3` | unclear — 19 unique commits, no PR |
| `release-2.1.3` | matches the 2.1.3 production baseline tested per the 3/13 update on #1187 (final confirmation pending — see Q2) |

### Historical naming-convention sprawl

Seven release-naming patterns in the history. Most are dead feature branches from years ago, not actual release lines, so they're cleanup targets rather than convention competitors:

| Pattern | Example | Active? |
|---|---|---|
| `releases/{semver}` ✅ | `releases/2.2.1` | yes (canonical) |
| `releases/v{semver}` | `releases/v2.0.0` | 2022, dead |
| `release-v{semver}` | `release-v2.2.0` | 2026, holds rolled-back 2.2.0 |
| `release-{semver}` | `release-2.1.3`, `release-2.1.1`, `release-1.3` | mix |
| `release-{semver}_candidate-{N}` | `release-2.1.3_candidate-2` | 2026, active testing PR |
| `release/{semver}` (singular) | `release/0.1.1` | dead — 0 unique commits, head is #1180 CI experiment |
| `internal_release_{semver}` | `internal_release_2.2` | 2025, dead |
| bare version | `v2.1` | 2022, dead |

`release/0.1.1` has `versionName "2.2.0"` in `build.gradle` — branch name doesn't match its file contents.

### Drift: `releases/2.2.1` is 3 ahead / 7 behind master

**On master, not on `releases/2.2.1`:**
- `#1282` splash crash (already cherry-picked as `#1284` ✓)
- `#1269` OkHttp 4.12.0 (likely forward-port)
- `#1273` Privacy "photos" plural (trivial forward-port)
- `#1266` revert PT translations (decision needed)
- `#1276` Dashboard refactor (probably skip — refactor scope)
- `#1264` tree editing (skip — new feature)
- `#1261` org links via Firebase config (skip — new feature)

**On `releases/2.2.1`, not on master:**
- `#1278` BDD framework
- `#1284` splash cherry of #1282
- `d49203b0` "Fix release gradle build"

No explicit in/out decision has been documented per commit.

### Stale branches

- 203 upstream branches total.
- 136 (≈67%) have no commit in the last 2 years.
- 68 are from 2022. Most predate the Kotlin migration. They are stale feature branches, not historical markers.

### Process docs

- `docs/release/releases.md` is a legacy notes page (last entry 2.1, 2023).
- No `docs/process/branching.md` or release-process document exists.
- The branching/tag question was raised on #1187 (2026-03-11):
> "the branching strategy for testing and release / whether a Git tag should be created to mark the testing baseline"

Subsequent discussion on that thread focused on environment setup. This issue resumes the branching/tag question.

---

## Why this matters for 2.2.1

1. No reproducible 2.2.0 artifact. Engineering can't `git checkout v2.2.0` to bisect or inspect what crashed.
2. No `v2.1.4` anchor for the currently-live rollback. Can't bisect 2.2.1 against the live production build.
3. Stale PR-source branches on upstream look like parallel release lines to newcomers — ambiguity at submission time.
4. `releases/2.2.1` has drifted from master without documented forward-port decisions.
5. **Pairs with #1123** (Firebase distribution to QA testers is currently not working). Tagging is the git-side anchor; once distribution is restored, QA builds can be pinned to a specific tag rather than "whichever branch tip happened to compile that day."

---

## Proposal

### Repo settings (one-click, by anyone with admin)

1. **Enable `Automatically delete head branches`** on the repo. Stops the stale-after-merge accumulation going forward. Fixes the `feat/bdd-for-2.2.1` class of problem at the source.
2. **Add branch protection on `master` and `releases/{*}`:** require PR before merge (status checks and linear history are optional add-ons — recommend leaving them off until CI is stable). Prevents direct pushes to upstream that bypass forks.

### Short-term, for 2.2.1 specifically

3. **Designate `releases/2.2.1` as the only 2.2.1 branch in writing**, in a comment on #1187 or this issue. All 2.2.1 PRs target it.
4. **Forward-port pass.** Decide per master commit whether it lands in 2.2.1; document the call in a comment. Suggested defaults: yes to #1269 (OkHttp), #1273 (Privacy text plural); skip refactor/features.
5. **Retroactively tag what we can:**
- `v2.2.0` → `9a64b4a9` (tip of `release-v2.2.0`, the rolled-back submission).
- `v2.1.4` → confirm SHA from Play Console; tag.
- `v2.1.3` → tip of `release-2.1.3` per the 3/13 update on #1187; confirm and tag.
6. **Tag the 2.2.1 submission at upload time** (not after): `git tag -a v2.2.1 -m "Play submission YYYY-MM-DD" && git push upstream v2.2.1`.

### One-time upstream cleanup (single ops pass)

7. **Delete the stale-after-merge / dead branches:**
- `feat/bdd-for-2.2.1` (merged via #1278)
- `release/0.1.1` (0 unique commits, dead CI experiment)
- `feat/bdd-test-releases-2.2.1` (abandoned WIP; confirm with the branch author before deleting)
- `main` (525 commits behind master, 0 ahead, last touched 2021-12-03). The repo default is `master`; `main` is a pre-Kotlin-era leftover. It's actively confusing to new contributors who assume `main` is the default per the GitHub-wide convention and may target PRs at it.
8. **Archive long-dead branches as tags.** For branches with no commit in 2+ years: tag the tip as `archive/{branch-name}`, then delete. ≈136 branches. Single batch operation. Preserves history.
9. **Leave alone:** `jonathanmuller/merge-release-2.2.1` (active PR #1260), `release-2.1.3_candidate-2` (active testing PR #1193). Confirm with the branch owners before touching `feat/rollback-release-2.1.3` and `feat/bdd-for-2.1.3`.

#### Why this doesn't lose revert capability or history

- Every deleted branch gets an `archive/{name}` tag first. Tags are permanent — `git checkout archive/release-v2.2.0` rebuilds the branch tip any time.
- Submitted releases get `v{semver}` tags at the exact SHA. Revert speed actually goes **up**: today there is no `v2.1.4` anchor for the currently-live rollback; after this, `git checkout v2.1.4` is enough to rebuild and resubmit.
- `delete_branch_on_merge` only deletes the PR-source branch. The merge commit on the target retains full content; GitHub keeps PR diffs/comments forever; a "Restore branch" button stays on closed PRs for ~90 days.
- Issues are branch-independent — reopening one doesn't require the branch that fixed it. Stale local clones recover with `git fetch --prune`.

### Document it (≤ 1-page doc)

10. **Add `docs/process/branching-and-releases.md`**, link from `docs/README.md`:
- Fork → personal branch on fork → PR to upstream. Personal branches do not live on upstream.
- Long-lived `releases/{major.minor}` per minor release line. No other release-naming patterns.
- Hotfix branches branch from `releases/{major.minor}`, merge back into that branch AND master.
- Every Play Store submission gets a `v{semver}` tag at the submitted commit, pushed to upstream.

### Effort

≈3–4 hours total — repo settings (15 min), retroactive tagging once SHAs are confirmed (1 h), branch-cleanup script (1 h), branching doc (1 h). Ongoing cost: zero; the settings prevent recurrence.

---

## Non-asks (intentionally out of scope)

- Not proposing CI changes, release automation, or release-please-style bots.
- Not proposing renaming `master` → `main`.
- Not proposing changing how `versionCode` / `versionName` are computed.
- Not proposing fixes to the Firebase distribution to QA — tracked separately at #1123. This proposal complements that work but does not depend on or block it.

---

## Open questions for the team

1. Play Console access: can someone confirm the SHAs that shipped for 2.2.0 (rolled back) and 2.1.4 (currently live)? Needed to retroactively tag.
2. Is `release-2.1.3` the verified 2.1.3 baseline per the 3/13 update on #1187, or `release-2.1.3_candidate-2`? They diverge at versionCode 195 vs 198.
3. Of the 6 unresolved master commits ahead of `releases/2.2.1` (#1269, #1273, #1266, #1276, #1264, #1261; #1282 is already cherry-picked), which forward-port and which are deliberately skipped?
4. Are `feat/rollback-release-2.1.3` and `feat/bdd-for-2.1.3` still active, or safe to archive?
5. Who owns approving the `docs/process/branching-and-releases.md` doc once drafted?

---

## Links

- #1187 (Android Release 2.2 Coordination — open coordination thread)
- #1260 (open PR — merge release changes into master)
- #1193 (open PR — "DO NOT MERGE" testing branch on `release-2.1.3_candidate-2`)
- #1123 (Get Firebase services working — distribution-side counterpart to this proposal)
- #1144 (BUG in creating signed release aab/apk — symptom of brittle release process)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the observed branch and tag state, the GitHub repository settings, and the open questions linked to #1187, #1260, and #1193. Review docs/release/releases.md and then draft docs/process/branching-and-releases.md, linking it from docs/README.md. Done means the team has resolved the release-baseline questions, documented the convention, and completed only the approved settings, tagging, and cleanup actions.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, github
Domain
devops, documentation, release
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.