+ # Pijul feature channel merge and unmerge workflows
+
+ This guide is written for both humans and automation/agents.
+
+ It describes a simple way to:
+
+ - keep a cleaner, more authoritative baseline channel such as `mainer-main`,
+ - pull a feature channel into an integration channel such as `staging`, and
+ - later remove that feature from `staging` without throwing away unrelated work.
+
+ ## The three-channel model
+
+ Use three kinds of channels:
+
+ - `mainer-main`: your cleaner baseline or "keep set". It can be ahead of `staging`, but it must not include the feature you may want to remove later.
+ - `feature-login`: a channel that represents one feature. Keep it around if you may want to add or remove that feature later.
+ - `staging`: a mix-and-match channel where you pull features in and out.
+
+ The key idea is:
+
+ > To remove a feature later, compute `feature - mainer-main`, then unrecord those hashes from `staging`.
+
+ ## Important command choice: `fork` vs `channel new`
+
+ Use `pijul fork` when you want a new channel that starts from an existing channel:
+
+ ```bash
+ pijul fork --channel main mainer-main
+ pijul fork --channel mainer-main staging
+ pijul fork --channel mainer-main feature-login
+ ```
+
+ Use `pijul channel new` only when you want an empty channel.
+
+ ## One-time setup
+
+ If you do not already have the channels you want:
+
+ ```bash
+ # Make a baseline copy of your main line.
+ pijul fork --channel main mainer-main
+
+ # Make an integration channel from that baseline.
+ pijul fork --channel mainer-main staging
+
+ # Make a feature channel from the same baseline.
+ pijul fork --channel mainer-main feature-login
+ ```
+
+ If those channels already exist, do not recreate them.
+
+ ## Workflow 1: merge a feature into `staging`
+
+ ### 1. Record the feature on its own channel
+
+ ```bash
+ pijul channel switch feature-login
+ # edit files
+ pijul record -a -m "feat(login): add login flow"
+ ```
+
+ ### 2. Pull that feature into `staging`
+
+ You can switch to `staging` first:
+
+ ```bash
+ pijul channel switch staging
+ pijul pull . --from-channel feature-login -a --no-prompt
+ ```
+
+ Or target `staging` directly without switching:
+
+ ```bash
+ pijul pull . --from-channel feature-login --to-channel staging -a --no-prompt
+ ```
+
+ ### 3. Review what `staging` now has compared to the baseline
+
+ ```bash
+ pijul channel switch staging
+ pijul diff --channel mainer-main --short
+ pijul log --channel staging --hash-only
+ ```
+
+ Useful spot checks:
+
+ ```bash
+ pijul log --channel feature-login --hash-only
+ pijul change <hash>
+ ```
+
+ ## Workflow 2: remove a feature from `staging`
+
+ This is the reverse workflow.
+
+ Do **not** pipe every hash from `feature-login` straight to `pijul unrecord`. A feature channel usually contains shared base history too, and you do not want to remove that from `staging`.
+
+ Instead:
+
+ 1. compare the feature channel to a baseline that does **not** contain the feature, and
+ 2. unrecord only the hashes that are unique to the feature channel.
+
+ ### 1. Preview the hashes that belong only to the feature
+
+ This prints hashes that are in `feature-login` but not in `mainer-main`:
+
+ ```bash
+ comm -13 \
+ <(pijul log --channel mainer-main --hash-only | sort) \
+ <(pijul log --channel feature-login --hash-only | sort)
+ ```
+
+ If this list is empty, either:
+
+ - the feature channel has no unique recorded changes, or
+ - your baseline already contains the feature, which means it is not the right baseline for unmerging.
+
+ ### 2. Inspect the candidate hashes before removing them
+
+ ```bash
+ comm -13 \
+ <(pijul log --channel mainer-main --hash-only | sort) \
+ <(pijul log --channel feature-login --hash-only | sort) \
+ | xargs -r -n 1 pijul change
+ ```
+
+ ### 3. Unrecord those feature-only hashes from `staging`
+
+ This Bash snippet gathers the hashes first, then passes them to a single `unrecord` command:
+
+ ```bash
+ mapfile -t feature_only_hashes < <(
+ comm -13 \
+ <(pijul log --channel mainer-main --hash-only | sort) \
+ <(pijul log --channel feature-login --hash-only | sort)
+ )
+
+ if ((${#feature_only_hashes[@]})); then
+ pijul unrecord --channel staging --reset --no-prompt "${feature_only_hashes[@]}"
+ else
+ echo "No feature-only hashes found."
+ fi
+ ```
+
+ `--reset` updates the working copy so `staging` actually reflects the removed feature.
+
+ ### 4. Review `staging` after the unmerge
+
+ ```bash
+ pijul channel switch staging
+ pijul diff --channel mainer-main --short
+ pijul log --channel staging --hash-only
+ ```
+
+ ## Useful command strings
+
+ ### Merge a feature into `staging` in one command
+
+ ```bash
+ pijul pull . --from-channel feature-login --to-channel staging -a --no-prompt
+ ```
+
+ ### Print the feature-only hashes
+
+ ```bash
+ comm -13 \
+ <(pijul log --channel mainer-main --hash-only | sort) \
+ <(pijul log --channel feature-login --hash-only | sort)
+ ```
+
+ ### Inspect why a change cannot be removed cleanly
+
+ If `pijul unrecord` refuses because later work depends on a feature hash, inspect dependents:
+
+ ```bash
+ pijul dependents <hash>
+ ```
+
+ That will show changes that depend on the hash you are trying to remove.
+
+ ## Rules of thumb
+
+ - Keep feature channels around while you may still want to pull or unpull them elsewhere.
+ - `mainer-main` does not need to be an exact pre-merge snapshot. It can be "mainer" than `staging` as long as it does not contain the feature you want to subtract.
+ - The baseline is a "keep set": whatever is already in `mainer-main` is treated as something you want to preserve.
+ - If `staging` has later changes that depend on the feature, `pijul unrecord` may refuse until you also remove those dependent changes.
+ - `pijul apply` reads change text from standard input, not hash lists. For hash-driven workflows, pass hashes as command arguments instead.
+
+ ## Short version
+
+ Merge in:
+
+ ```bash
+ pijul pull . --from-channel feature-login --to-channel staging -a --no-prompt
+ ```
+
+ Unmerge out:
+
+ ```bash
+ mapfile -t feature_only_hashes < <(
+ comm -13 \
+ <(pijul log --channel mainer-main --hash-only | sort) \
+ <(pijul log --channel feature-login --hash-only | sort)
+ )
+
+ if ((${#feature_only_hashes[@]})); then
+ pijul unrecord --channel staging --reset --no-prompt "${feature_only_hashes[@]}"
+ fi
+ ```
+
+ That is the core pattern:
+
+ - **merge** with `pijul pull`, and
+ - **unmerge** with `source-minus-baseline`, then `pijul unrecord --reset`.