GitoxideLabs/gitoxide · error
A reference named ' ' has multiple edits
Error message
A reference named '{name}' has multiple edits What it means
During reference transaction pre-processing, gix-ref checks that each reference name appears in at most one edit; if `assure_one_name_has_one_edit` finds duplicates, pre_process returns an io::Error of kind AlreadyExists: "A reference named '{name}' has multiple edits". Git semantics forbid two conflicting updates to the same ref within one transaction.
Solutions
- Deduplicate RefEdits by reference name before preparing the transaction (later edit wins)
- Split conflicting operations into separate sequential transactions
- Audit code paths that build edit lists to ensure they don't both touch the same ref
- If intentional replace is needed, remove the earlier edit rather than appending a second one
Example fix
// before
let tx = repo.edit_reference(edits::Reference::Create(name, target1, msg));
// ... elsewhere
transaction.prepare(edits2) // edits2 contains a second edit for `name`
// after
let mut edits: Vec<RefEdit> = edits1.into_iter()
.chain(edits2)
.fold(BTreeMap::new(), |mut m, e| { m.insert(e.name.clone(), e); m })
.into_values().collect(); Defensive patterns
Strategy: validation
Validate before calling
// before preparing the transaction
echo "checking for duplicate ref edits" >&2;
let mut names = std::collections::HashSet::new();
for edit in &edits {
if !names.insert(edit.name.as_bstr().to_vec()) {
return Err(format!("duplicate edit for {}", edit.name));
}
} Prevention
- Deduplicate edits by ref name before transaction prepare
- Never queue multiple edits for the same ref in one transaction
- Centralize ref-edit construction to avoid double-queueing
When it happens
Trigger: Calling `gix_ref::transaction::Transaction::pre_process` (with a `find` lookup and `make_entry` factory) after adding two RefEdits for the same reference name into the same transaction.
Common situations: Application logic queuing e.g. both a create and an update for HEAD or the same branch; concurrent code paths each adding an edit for the same ref; migration scripts replaying edits twice.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- change ID is ambiguous in the Tix view; candidates
- already has saved worktree state
- a commit is picked more than once
- Could not follow all splits after
- Tried to use as tree, but was
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/30fab49f0fd19841.
Report an issue: GitHub.
Appendix: source
Thrown at gix-ref/src/transaction/ext.rs:35
///
/// Note no action is performed if deref isn't specified.
fn extend_with_splits_of_symbolic_refs(
&mut self,
find: &mut dyn FnMut(&PartialNameRef) -> Option<Target>,
make_entry: &mut dyn FnMut(usize, RefEdit) -> T,
) -> Result<(), std::io::Error>;
/// All processing steps in one and in the correct order.
///
/// Users call this to assure derefs are honored and duplicate checks are done.
fn pre_process(
&mut self,
find: &mut dyn FnMut(&PartialNameRef) -> Option<Target>,
make_entry: &mut dyn FnMut(usize, RefEdit) -> T,
) -> Result<(), std::io::Error> {
self.extend_with_splits_of_symbolic_refs(find, make_entry)?;
self.assure_one_name_has_one_edit().map_err(|name| {
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("A reference named '{name}' has multiple edits"),
)
})
}
}
impl<E> RefEditsExt<E> for Vec<E>
where
E: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>,
{
fn assure_one_name_has_one_edit(&self) -> Result<(), BString> {
let mut names: Vec<_> = self.iter().map(|e| &e.borrow().name).collect();
names.sort();
match names.windows(2).find(|v| v[0] == v[1]) {
Some(name) => Err(name[0].as_bstr().to_owned()),
None => Ok(()),
}View on GitHub (pinned to e73179060b)