Lab 2.2: Prepare a PR Using OpenSearch Practices
Background
This lab is the full mechanical pipeline of an OpenSearch pull request, end to end, with a trivial
change so the workflow — not the code — is the lesson. You will fork, clone, branch, make a tiny
doc/test change, add a CHANGELOG.md entry, format with Spotless, run precommit, make a signed
commit (git commit -s for DCO), push, and open a PR against main. You will see a real diff and
a real Signed-off-by commit message, understand the PR template and each CI check, and learn how
the backport 2.x label works.
Do this once with a throwaway change so that when you do Lab 2.3 for real, the plumbing is invisible.
Why This Lab Matters for Contributors
- Every PR you ever open follows this exact sequence. Internalize it and you stop thinking about mechanics and start thinking about the change.
- DCO and CHANGELOG are blocking CI checks; getting them right locally avoids the two most common first-PR failures.
- Running
spotlessApply+precommitlocally is what turns a five-round review into one.
Prerequisites
- Lab 2.1 complete; you can navigate the repo.
- A GitHub account,
gitconfigured, and theghCLI (optional but convenient). - Your git identity set correctly — this becomes your DCO sign-off:
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
git config user.name; git config user.email # verify; these MUST match your Signed-off-by
Step-by-Step Tasks
Step 1: Fork and Clone
Fork opensearch-project/OpenSearch to your account (the Fork button on GitHub, or
gh repo fork). Then clone your fork and wire the canonical repo as upstream:
# Clone your fork (replace YOURNAME):
git clone https://github.com/YOURNAME/OpenSearch.git
cd OpenSearch
# Add the canonical repo as 'upstream' so you can keep main current:
git remote add upstream https://github.com/opensearch-project/OpenSearch.git
git remote -v
# origin https://github.com/YOURNAME/OpenSearch.git (fetch/push) <- your fork
# upstream https://github.com/opensearch-project/OpenSearch.git (fetch/push)
gh does this in one step:
gh repo fork opensearch-project/OpenSearch --clone=true --remote=true
Step 2: Sync main and Branch
Always branch from an up-to-date main:
git checkout main
git fetch upstream
git merge --ff-only upstream/main # fast-forward your local main to upstream
git push origin main # keep your fork's main current too
# Create a topic branch (name it after the change):
git checkout -b docs/clarify-bulk-example
Note: Branch off
main. Fixes land onmainfirst and are backported to2.xlater via thebackport 2.xlabel — you do not branch off2.xfor a new fix.
Step 3: Make a Trivial Change
For this dry run, pick something harmless and real — for example, fix a small inaccuracy or add a clarifying sentence to an in-repo doc, or tighten a test's assertion message. Keep it to one logical change. Suppose you correct a stale example in a developer doc:
# Find a candidate (illustrative grep — look for a fixable doc nit):
grep -rn "localhost:9200" DEVELOPER_GUIDE.md TESTING.md 2>/dev/null | head
Make the edit in your editor. The resulting diff should be small and obviously correct:
diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md
index abc1234..def5678 100644
--- a/DEVELOPER_GUIDE.md
+++ b/DEVELOPER_GUIDE.md
@@ -212,7 +212,7 @@ To run a single test class:
- ./gradlew test --tests "org.opensearch.ExampleTests"
+ ./gradlew :server:test --tests "org.opensearch.ExampleTests"
This is deliberately tiny. The discipline of "one logical change, obviously correct" is what you are practicing — not the change itself.
Step 4: Add a CHANGELOG Entry
Open CHANGELOG.md, find the ## [Unreleased ...] heading, and add one line in the appropriate
subsection (Added / Changed / Fixed / Deprecated / Removed). A doc fix usually goes under
Fixed or Changed:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1111aaa..2222bbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@
## [Unreleased 3.x]
### Added
### Changed
+- Correct the single-test example in the developer guide to use the `:server:test` task ([#NNNNN](https://github.com/opensearch-project/OpenSearch/pull/NNNNN))
### Fixed
You will not know the PR number yet — use a placeholder (#NNNNN) and update it after the PR opens,
or many contributors push, read the assigned number, then amend. CI checks that an entry exists,
not that the number is final.
Note: Truly trivial, non-user-facing changes can sometimes carry the "skip changelog" label instead of an entry — but default to adding one. A missing CHANGELOG is the #1 first-PR nit.
Step 5: Format with Spotless
Even for a doc/test change, run Spotless so formatting never blocks you:
./gradlew spotlessApply # auto-format any Java you touched
./gradlew spotlessJavaCheck # verify (this is what CI checks)
For a pure-Markdown change this is a no-op for the formatter, but make it a reflex — the moment you
touch a .java, Spotless matters.
Step 6: Run Precommit (and Scoped Tests if You Touched Code)
./gradlew precommit
# If you changed a test or source file, also run the scoped test:
# ./gradlew :server:test --tests "org.opensearch.ExampleTests"
A green precommit locally means the CI precommit check will be green too. This is the highest-
leverage habit in the whole workflow (from Lab 1.2).
Step 7: Commit with DCO Sign-off
The -s flag is mandatory — it appends the Signed-off-by line the DCO check requires:
git add DEVELOPER_GUIDE.md CHANGELOG.md
git commit -s -m "Use the :server:test task in the single-test developer-guide example"
Inspect the resulting commit — note the trailer:
git log -1 --format=full
commit a1b2c3d4...
Author: Your Name <your.email@example.com>
Commit: Your Name <your.email@example.com>
Use the :server:test task in the single-test developer-guide example
Signed-off-by: Your Name <your.email@example.com>
The Signed-off-by name/email must match your git identity. If you forgot -s, fix it without
re-doing the work:
git commit --amend -s --no-edit # add sign-off to the latest commit
# or, across multiple commits on the branch:
git rebase --signoff upstream/main
Step 8: Push and Open the PR
git push origin docs/clarify-bulk-example
GitHub prints a "create a pull request" URL, or use gh:
gh pr create --repo opensearch-project/OpenSearch --base main \
--title "Use the :server:test task in the single-test developer-guide example" \
--body "See template below."
When the PR opens, GitHub pre-fills .github/pull_request_template.md. Fill every section honestly:
### Description
Corrects the single-test example in DEVELOPER_GUIDE.md to use the `:server:test`
Gradle path, matching the project layout. Pure documentation change.
### Related Issues
Resolves #NNNNN <!-- or "N/A" if you filed no issue for a trivial doc fix -->
### Check List
- [x] New functionality includes testing. (N/A — docs only)
- [x] New functionality has been documented.
- [x] API changes companion pull request created, if applicable.
- [x] Commits are signed per the DCO using `--signoff`.
- [x] Changelog updated, or "skip changelog" justified.
Step 9: Read the CI Checks
Once opened, CI runs. Each check maps to something you can reproduce locally:
| CI check | Verifies | If it's red |
|---|---|---|
| DCO | Every commit has a valid Signed-off-by. | git commit --amend -s / git rebase --signoff, force-push the branch. |
| Changelog | An entry exists under ## [Unreleased] (or skip label). | Add the line; push. |
| precommit | Checkstyle, forbidden APIs, SPDX headers, etc. | ./gradlew precommit locally; fix; push. |
| assemble | The distribution still builds. | ./gradlew assemble. |
| gradle-check | Build + relevant tests + precommit across affected projects. | Reproduce the failing test locally with its -Dtests.seed. |
Note: Some CI workflows require a maintainer to comment to start the heavy
gradle-checkon a first-time contributor's PR (a security gate against running arbitrary code). Be patient; do not spam pushes to retrigger.
Step 10: Respond to Review and the Backport Label
A maintainer (listed in MAINTAINERS.md) reviews. For each comment:
- Make the change as a new signed commit on the same branch and push. Do not force-push away the review history unless a maintainer asks you to squash.
- Reply to each comment, marking resolved when addressed.
# Address a review comment:
# ...edit...
git add -A && git commit -s -m "Address review: reword the example caption"
git push origin docs/clarify-bulk-example
When approved, a maintainer squash-merges to main. If the fix should also go to the maintenance
line, the backport 2.x label is applied (by a maintainer, or by you if you have the rights).
A bot then opens a backport PR cherry-picking your squashed commit onto 2.x; you may need to
resolve conflicts there. Responding to feedback well is its own skill —
Responding to Maintainer Feedback.
Implementation Requirements
Deliverables (you may open the PR as a draft and close it afterward — the goal is the mechanics):
-
A fork with
upstreamconfigured and a localmainfast-forwarded toupstream/main. -
A topic branch off
mainwith one small, obviously-correct change. -
A
CHANGELOG.mdentry under## [Unreleased]. -
A clean
./gradlew spotlessApplyand./gradlew precommit. -
A commit whose
git log -1 --format=fullshows aSigned-off-bymatching your git identity. - A pushed branch and an opened (draft) PR with the template fully filled in.
- A written description of what each of the five CI checks verifies and how to fix a red one.
Troubleshooting
DCO check is red: "Commit sha … does not have a valid sign-off"
The commit lacks (or has a mismatched) Signed-off-by. Fix and force-push the branch:
git rebase --signoff upstream/main # add sign-off to every commit on the branch
git push --force-with-lease origin docs/clarify-bulk-example
(Force-pushing your own unmerged topic branch to fix DCO/rebase is fine; force-pushing away a maintainer's in-progress review is not.)
Changelog check is red
You did not add (or you mis-placed) the entry. It must be under the ## [Unreleased] heading in the
correct subsection. Re-check the heading name matches your target line (3.x for main).
git merge --ff-only upstream/main fails
Your local main has diverged (you committed on it by accident). Reset it to upstream:
git checkout main
git fetch upstream
git reset --hard upstream/main
Never commit on main; always branch.
precommit fails on a file you did not touch
Confirm it is pre-existing on a clean main (git stash; ./gradlew precommit; git stash pop). If
main is clean, the failure is yours — fix it.
Expected Output
A correct signed commit:
$ git log -1 --format='%an <%ae>%n%n%B'
Your Name <your.email@example.com>
Use the :server:test task in the single-test developer-guide example
Signed-off-by: Your Name <your.email@example.com>
A green local gate:
$ ./gradlew spotlessJavaCheck precommit
BUILD SUCCESSFUL in 5m 12s
Stretch Goals
-
Practice the backport mentally. Read
.github/workflows/for the backport workflow file andgrep -rn "backport" .github/. Identify which label triggers the bot. -
Inspect the PR template source.
cat .github/pull_request_template.mdand map each checkbox to a CI check or a maintainer expectation. -
Amend versus new commit. Practice both:
git commit --amend -s(rewrites the last commit) and a freshgit commit -s(adds a new one). Know when each is appropriate during review (new commits during review; amend only before the first push or when asked to squash). -
Keep a long-lived branch current. While your PR sits in review,
mainmoves. Practice:git fetch upstream && git rebase upstream/main && git push --force-with-lease.
Validation / Self-check
You are done when you can answer these without notes:
- What does
git commit -sadd, and why is it required? What must it match? - Where exactly does a CHANGELOG entry go, and what happens in CI if it is missing?
- Which two Gradle tasks should you run locally before pushing, and why does that speed up review?
- Why do you branch off
mainand not2.xfor a new fix? How does the fix reach2.x? - When is force-pushing your branch acceptable, and when is it not?
- Name the five CI checks and, for each, the local command that reproduces it.
Next: Lab 2.3 — Fix It: A Good First Issue, where the change is real.