Skip to content

Commit 8c36f0b

Browse files
committed
feat(submit:phabricator): do not abort entire process on failure
Currently, `git submit` for Phabricator will abort the entire operation if any commit fails to be submitted. This means that if `arc diff` succeeds on one commit and then fails on its child, the entire operation is aborted. However, the first `arc diff` had side effects, so the user gets diffs uploaded to Phabricator that are not reflected locally. Instead, we should confirm any passing commits and abort after we get a failing commit. This commit updates the Phabricator forge to handle the error case better and not produce garbage commits on Phabricator.
1 parent 94889b3 commit 8c36f0b

File tree

6 files changed

+285
-91
lines changed

6 files changed

+285
-91
lines changed

git-branchless-lib/src/core/eventlog.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ INSERT INTO event_log VALUES (
580580
///
581581
/// Returns: All the events in the database, ordered from oldest to newest.
582582
#[instrument]
583-
584583
pub fn get_events(&self) -> eyre::Result<Vec<Event>> {
585584
let mut stmt = self.conn.prepare(
586585
"

git-branchless-submit/src/branch_forge.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ These remotes are available: {}",
253253
commit_status.local_commit_name.map(|local_commit_name| {
254254
(
255255
commit_oid,
256-
CreateStatus {
256+
CreateStatus::Created {
257257
final_commit_oid: commit_oid,
258258
local_commit_name,
259259
},

git-branchless-submit/src/github.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,25 @@ impl Forge for GithubForge<'_> {
289289
}),
290290
options
291291
)?);
292+
for (commit_oid, create_status) in created_branch.iter() {
293+
match create_status {
294+
CreateStatus::Created { .. } => {}
295+
CreateStatus::Skipped | CreateStatus::Err => {
296+
// FIXME: surface the inner branch forge error somehow?
297+
writeln!(
298+
effects.get_output_stream(),
299+
"Could not create branch for commit: {}",
300+
effects.get_glyphs().render(
301+
self.repo.friendly_describe_commit_from_oid(
302+
effects.get_glyphs(),
303+
*commit_oid
304+
)?,
305+
)?
306+
)?;
307+
return Ok(Err(ExitCode(1)));
308+
}
309+
}
310+
}
292311
created_branches.extend(created_branch.into_iter());
293312
}
294313

@@ -297,7 +316,7 @@ impl Forge for GithubForge<'_> {
297316
.copied()
298317
.map(|(commit_oid, commit_status)| {
299318
let commit_status = match created_branches.get(&commit_oid) {
300-
Some(CreateStatus {
319+
Some(CreateStatus::Created {
301320
final_commit_oid: _,
302321
local_commit_name,
303322
}) => CommitStatus {
@@ -308,11 +327,18 @@ impl Forge for GithubForge<'_> {
308327
// Expecting this to be the same as the local branch name (for now):
309328
remote_commit_name: Some(local_commit_name.clone()),
310329
},
330+
331+
Some(
332+
CreateStatus::Skipped | CreateStatus::Err ,
333+
) => {
334+
warn!(?commits_to_create, ?created_branches, ?commit_oid, ?commit_status, "commit failed to be created");
335+
eyre::bail!("BUG: should have been handled in previous call to branch_forge.create: {commit_oid:?} has status {commit_status:?}");
336+
}
311337
None => commit_status.clone(),
312338
};
313-
(commit_oid, commit_status)
339+
Ok((commit_oid, commit_status))
314340
})
315-
.collect();
341+
.try_collect()?;
316342

317343
// Create the pull requests only after creating all the branches because
318344
// we rely on the presence of a branch on each commit in the stack to

git-branchless-submit/src/lib.rs

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,28 @@ pub struct SubmitOptions {
140140

141141
/// The result of creating a commit.
142142
#[derive(Clone, Debug)]
143-
pub struct CreateStatus {
144-
/// The commit OID after carrying out the creation process. Usually, this
145-
/// will be the same as the original commit OID, unless the forge amends it
146-
/// (e.g. to include a change ID).
147-
pub final_commit_oid: NonZeroOid,
148-
149-
/// An identifier corresponding to the commit, for display purposes only.
150-
/// This may be a branch name, a change ID, the commit summary, etc.
151-
///
152-
/// This does not necessarily correspond to the commit's name/identifier in
153-
/// the forge (e.g. not a code review link).
154-
pub local_commit_name: String,
143+
pub enum CreateStatus {
144+
/// The commit was successfully created.
145+
Created {
146+
/// The commit OID after carrying out the creation process. Usually, this
147+
/// will be the same as the original commit OID, unless the forge amends it
148+
/// (e.g. to include a change ID).
149+
final_commit_oid: NonZeroOid,
150+
151+
/// An identifier corresponding to the commit, for display purposes only.
152+
/// This may be a branch name, a change ID, the commit summary, etc.
153+
///
154+
/// This does not necessarily correspond to the commit's name/identifier in
155+
/// the forge (e.g. not a code review link).
156+
local_commit_name: String,
157+
},
158+
159+
/// The commit was skipped. Possibly it had already been created, or it
160+
/// couldn't be created due to a previous commit's error.
161+
Skipped,
162+
163+
/// An error occurred while trying to create the commit.
164+
Err,
155165
}
156166

157167
/// "Forge" refers to a Git hosting provider, such as GitHub, GitLab, etc.
@@ -369,30 +379,52 @@ fn submit(
369379
(local, unsubmitted, to_update, to_skip)
370380
});
371381

372-
let (submitted_commit_names, unsubmitted_commit_names): (BTreeSet<String>, BTreeSet<String>) = {
382+
let (submitted_commit_names, unsubmitted_commit_names, create_error_commits): (
383+
BTreeSet<String>,
384+
BTreeSet<String>,
385+
BTreeSet<NonZeroOid>,
386+
) = {
373387
let unsubmitted_commit_names: BTreeSet<String> = unsubmitted_commits
374388
.values()
375389
.flat_map(|commit_status| commit_status.local_commit_name.clone())
376390
.collect();
377391
if create {
378-
let created_commit_names = if dry_run {
379-
unsubmitted_commit_names.clone()
392+
if dry_run {
393+
(
394+
unsubmitted_commit_names.clone(),
395+
Default::default(),
396+
Default::default(),
397+
)
380398
} else {
381399
let create_statuses =
382400
try_exit_code!(forge.create(unsubmitted_commits, &submit_options)?);
383-
create_statuses
384-
.into_values()
385-
.map(
386-
|CreateStatus {
387-
final_commit_oid: _,
388-
local_commit_name,
389-
}| local_commit_name,
390-
)
391-
.collect()
392-
};
393-
(created_commit_names, Default::default())
401+
let mut submitted_commit_names = BTreeSet::new();
402+
let mut error_commits = BTreeSet::new();
403+
for (commit_oid, create_status) in create_statuses {
404+
match create_status {
405+
CreateStatus::Created {
406+
final_commit_oid: _, // currently not rendered
407+
local_commit_name,
408+
} => {
409+
submitted_commit_names.insert(local_commit_name);
410+
}
411+
// For now, treat `Skipped` the same as `Err` as it would be
412+
// a lot of work to render it differently in the output, and
413+
// we may want to rethink the data structures before doing
414+
// that.
415+
CreateStatus::Skipped | CreateStatus::Err => {
416+
error_commits.insert(commit_oid);
417+
}
418+
}
419+
}
420+
(submitted_commit_names, Default::default(), error_commits)
421+
}
394422
} else {
395-
(Default::default(), unsubmitted_commit_names)
423+
(
424+
Default::default(),
425+
unsubmitted_commit_names,
426+
Default::default(),
427+
)
396428
}
397429
};
398430

@@ -512,8 +544,33 @@ repository. To submit them, retry this operation with the --create option.",
512544
if dry_run { "are" } else { "were" },
513545
)?;
514546
}
547+
if !create_error_commits.is_empty() {
548+
writeln!(
549+
effects.get_output_stream(),
550+
"Failed to create {}:",
551+
Pluralize {
552+
determiner: None,
553+
amount: create_error_commits.len(),
554+
unit: ("commit", "commits")
555+
},
556+
)?;
557+
for error_commit_oid in &create_error_commits {
558+
let error_commit = repo.find_commit_or_fail(*error_commit_oid)?;
559+
writeln!(
560+
effects.get_output_stream(),
561+
"{}",
562+
effects
563+
.get_glyphs()
564+
.render(error_commit.friendly_describe(effects.get_glyphs())?)?
565+
)?;
566+
}
567+
}
515568

516-
Ok(Ok(()))
569+
if !create_error_commits.is_empty() {
570+
Ok(Err(ExitCode(1)))
571+
} else {
572+
Ok(Ok(()))
573+
}
517574
}
518575

519576
#[instrument]

0 commit comments

Comments
 (0)