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 + precommit locally is what turns a five-round review into one.

Prerequisites

  • Lab 2.1 complete; you can navigate the repo.
  • A GitHub account, git configured, and the gh CLI (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 on main first and are backported to 2.x later via the backport 2.x label — you do not branch off 2.x for 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 checkVerifiesIf it's red
DCOEvery commit has a valid Signed-off-by.git commit --amend -s / git rebase --signoff, force-push the branch.
ChangelogAn entry exists under ## [Unreleased] (or skip label).Add the line; push.
precommitCheckstyle, forbidden APIs, SPDX headers, etc../gradlew precommit locally; fix; push.
assembleThe distribution still builds../gradlew assemble.
gradle-checkBuild + 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-check on 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 upstream configured and a local main fast-forwarded to upstream/main.
  • A topic branch off main with one small, obviously-correct change.
  • A CHANGELOG.md entry under ## [Unreleased].
  • A clean ./gradlew spotlessApply and ./gradlew precommit.
  • A commit whose git log -1 --format=full shows a Signed-off-by matching 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

  1. Practice the backport mentally. Read .github/workflows/ for the backport workflow file and grep -rn "backport" .github/. Identify which label triggers the bot.

  2. Inspect the PR template source. cat .github/pull_request_template.md and map each checkbox to a CI check or a maintainer expectation.

  3. Amend versus new commit. Practice both: git commit --amend -s (rewrites the last commit) and a fresh git 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).

  4. Keep a long-lived branch current. While your PR sits in review, main moves. 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:

  1. What does git commit -s add, and why is it required? What must it match?
  2. Where exactly does a CHANGELOG entry go, and what happens in CI if it is missing?
  3. Which two Gradle tasks should you run locally before pushing, and why does that speed up review?
  4. Why do you branch off main and not 2.x for a new fix? How does the fix reach 2.x?
  5. When is force-pushing your branch acceptable, and when is it not?
  6. 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.