Skip to content

Commit 858e15c

Browse files
fix(record): support --create, advance branches, mark reachable
Fixes various defects encountered during testing.
1 parent 5d49307 commit 858e15c

2 files changed

Lines changed: 146 additions & 28 deletions

File tree

git-branchless-record/src/lib.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
use std::collections::HashSet;
1313
use std::ffi::OsString;
1414
use std::fmt::Write;
15-
use std::time::SystemTime;
15+
use std::time::{SystemTime, UNIX_EPOCH};
1616

1717
use chrono::DateTime;
18-
use eyre::OptionExt;
18+
use eyre::{OptionExt, WrapErr};
1919
use git_branchless_invoke::CommandContext;
2020
use git_branchless_opts::{MessageArgs, RecordArgs, ResolveRevsetOptions, Revset};
2121
use git_branchless_reword::{ResolveFixupCommitError, edit_message, resolve_commit_to_fixup};
@@ -24,8 +24,9 @@ use lib::core::check_out::{CheckOutCommitOptions, CheckoutTarget, check_out_comm
2424
use lib::core::config::{get_commit_template, get_restack_preserve_timestamps};
2525
use lib::core::dag::{CommitSet, Dag};
2626
use lib::core::effects::{Effects, OperationType};
27-
use lib::core::eventlog::{EventLogDb, EventReplayer, EventTransactionId};
27+
use lib::core::eventlog::{Event as LogEvent, EventLogDb, EventReplayer, EventTransactionId};
2828
use lib::core::formatting::Pluralize;
29+
use lib::core::gc::mark_commit_reachable;
2930
use lib::core::repo_ext::RepoExt;
3031
use lib::core::rewrite::{
3132
BuildRebasePlanError, BuildRebasePlanOptions, ExecuteRebasePlanOptions,
@@ -34,10 +35,10 @@ use lib::core::rewrite::{
3435
};
3536
use lib::core::untracked_file_cache::{UntrackedFileStrategy, process_untracked_files};
3637
use lib::git::{
37-
CategorizedReferenceName, ConfigRead, FileMode, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo,
38-
ResolvedReferenceInfo, Signature, Stage, UpdateIndexCommand, WorkingCopyChangesType,
39-
WorkingCopySnapshot, process_diff_for_record, summarize_diff_for_temporary_commit,
40-
update_index,
38+
CategorizedReferenceName, ConfigRead, FileMode, GitRunInfo, MaybeZeroOid, NonZeroOid,
39+
ReferenceName, Repo, ResolvedReferenceInfo, Signature, Stage, UpdateIndexCommand,
40+
WorkingCopyChangesType, WorkingCopySnapshot, process_diff_for_record,
41+
summarize_diff_for_temporary_commit, update_index,
4142
};
4243
use lib::try_exit_code;
4344
use lib::util::{ExitCode, EyreExitOr};
@@ -112,6 +113,7 @@ fn record(
112113
&event_log_db,
113114
&event_tx_id,
114115
messages,
116+
branch_name,
115117
)?);
116118
} else {
117119
try_exit_code!(create_commit_with_changes(
@@ -454,13 +456,14 @@ fn create_new_commit(
454456
event_log_db: &EventLogDb,
455457
event_tx_id: &EventTransactionId,
456458
messages: Vec<String>,
459+
branch_name: Option<String>,
457460
) -> EyreExitOr<()> {
458461
let head_info = repo.get_head_info()?;
459-
let current_commit_oid = match head_info {
462+
let (current_commit_oid, existing_ref_name) = match head_info {
460463
ResolvedReferenceInfo {
461464
oid: Some(oid),
462-
reference_name: _,
463-
} => oid,
465+
reference_name,
466+
} => (oid, reference_name),
464467
ResolvedReferenceInfo {
465468
oid: None,
466469
reference_name: _,
@@ -510,22 +513,46 @@ fn create_new_commit(
510513
vec![&current_commit],
511514
)?;
512515

513-
// TODO: move branch if ref_name
514-
// TODO: mark commit reachable (will show in sl if descendants, but not if detached)
515-
// FIXME: --new --create <branch> doesn't seem to work
516+
mark_commit_reachable(repo, new_oid)
517+
.wrap_err("Marking commit as reachable for GC purposes.")?;
518+
event_log_db.add_events(vec![LogEvent::CommitEvent {
519+
timestamp: now.duration_since(UNIX_EPOCH)?.as_secs_f64(),
520+
event_tx_id: *event_tx_id,
521+
commit_oid: new_oid,
522+
}])?;
523+
524+
let checkout_target = if let Some(name) = branch_name {
525+
// --create <branch>: create the new branch at the new commit.
526+
try_exit_code!(git_run_info.run(
527+
effects,
528+
Some(*event_tx_id),
529+
&["branch", &name, &new_oid.to_string()],
530+
)?);
531+
let ref_name = ReferenceName::from(format!("refs/heads/{name}"));
532+
CheckoutTarget::Reference(ref_name)
533+
} else if let Some(ref_name) = existing_ref_name {
534+
// On a named branch: advance it to the new commit, mirroring `git commit`.
535+
try_exit_code!(git_run_info.run(
536+
effects,
537+
Some(*event_tx_id),
538+
&["update-ref", ref_name.as_str(), &new_oid.to_string()],
539+
)?);
540+
CheckoutTarget::Reference(ref_name)
541+
} else {
542+
// Detached HEAD, no --create: check out by OID.
543+
CheckoutTarget::Oid(new_oid)
544+
};
516545

517546
try_exit_code!(check_out_commit(
518547
effects,
519548
git_run_info,
520549
repo,
521550
event_log_db,
522551
*event_tx_id,
523-
Some(CheckoutTarget::Oid(new_oid)),
552+
Some(checkout_target),
524553
&CheckOutCommitOptions {
525-
additional_args: vec![],
526-
force_detach: false,
527-
reset: false,
528554
render_smartlog: false,
555+
..Default::default()
529556
},
530557
)?);
531558

git-branchless-record/tests/test_record.rs

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,8 @@ fn test_record_new() -> eyre::Result<()> {
526526
},
527527
)?;
528528
insta::assert_snapshot!(stdout, @r###"
529-
branchless: running command: <git-executable> checkout f25fe40ff47319c8b8c61a32c03f2c3558aacadd --
529+
branchless: running command: <git-executable> update-ref refs/heads/master f25fe40ff47319c8b8c61a32c03f2c3558aacadd
530+
branchless: running command: <git-executable> checkout master --
530531
M test1.txt
531532
"###);
532533

@@ -542,9 +543,7 @@ fn test_record_new() -> eyre::Result<()> {
542543
let stdout = git.smartlog()?;
543544
insta::assert_snapshot!(stdout, @r###"
544545
:
545-
O 62fc20d (master) create test1.txt
546-
|
547-
@ f25fe40 empty commit 1
546+
@ f25fe40 (> master) empty commit 1
548547
"###);
549548
}
550549

@@ -561,7 +560,8 @@ fn test_record_new() -> eyre::Result<()> {
561560
},
562561
)?;
563562
insta::assert_snapshot!(stdout, @r###"
564-
branchless: running command: <git-executable> checkout 46ba0c4efce07dc705498e7b79f7116d4e9ef7a3 --
563+
branchless: running command: <git-executable> update-ref refs/heads/master 46ba0c4efce07dc705498e7b79f7116d4e9ef7a3
564+
branchless: running command: <git-executable> checkout master --
565565
M test1.txt
566566
"###);
567567

@@ -577,11 +577,7 @@ fn test_record_new() -> eyre::Result<()> {
577577
let stdout = git.smartlog()?;
578578
insta::assert_snapshot!(stdout, @r###"
579579
:
580-
O 62fc20d (master) create test1.txt
581-
|
582-
o f25fe40 empty commit 1
583-
|
584-
@ 46ba0c4 empty commit 2
580+
@ 46ba0c4 (> master) empty commit 2
585581
"###);
586582
}
587583

@@ -624,7 +620,8 @@ fn test_record_new_uses_user_name_and_email() -> eyre::Result<()> {
624620
},
625621
)?;
626622
insta::assert_snapshot!(stdout, @r###"
627-
branchless: running command: <git-executable> checkout d4aea9dcdb2ad9ddedc964de9e782aad5a97b864 --
623+
branchless: running command: <git-executable> update-ref refs/heads/master d4aea9dcdb2ad9ddedc964de9e782aad5a97b864
624+
branchless: running command: <git-executable> checkout master --
628625
M test1.txt
629626
"###);
630627

@@ -1363,3 +1360,97 @@ fn test_record_fixup() -> eyre::Result<()> {
13631360

13641361
Ok(())
13651362
}
1363+
1364+
#[test]
1365+
fn test_record_new_moves_branch() -> eyre::Result<()> {
1366+
let git = make_git()?;
1367+
if !git.supports_reference_transactions()? {
1368+
return Ok(());
1369+
}
1370+
git.init_repo()?;
1371+
1372+
{
1373+
// Switch to a new branch and then create a new commit. The branch
1374+
// pointer should advance with the new commit.
1375+
git.run(&["switch", "--create", "test"])?;
1376+
git.branchless("record", &["-m", "empty commit 1", "--new"])?;
1377+
1378+
let stdout = git.smartlog()?;
1379+
insta::assert_snapshot!(stdout, @r###"
1380+
O f777ecc (master) create initial.txt
1381+
|
1382+
@ 1fa4693 (> test) empty commit 1
1383+
"###);
1384+
}
1385+
1386+
Ok(())
1387+
}
1388+
1389+
#[test]
1390+
fn test_record_new_marks_commit_reachable() -> eyre::Result<()> {
1391+
let git = make_git()?;
1392+
if !git.supports_reference_transactions()? {
1393+
return Ok(());
1394+
}
1395+
git.init_repo()?;
1396+
git.commit_file("test1", 1)?;
1397+
git.detach_head()?;
1398+
1399+
let stdout = git.smartlog()?;
1400+
insta::assert_snapshot!(stdout, @r###"
1401+
:
1402+
@ 62fc20d (master) create test1.txt
1403+
"###);
1404+
1405+
{
1406+
// Create new commit then switch back to master.
1407+
git.branchless("record", &["-m", "empty commit 1", "--new"])?;
1408+
git.run(&["switch", "master"])?;
1409+
1410+
// The new commit should still appear in the smartlog even though no
1411+
// branch points to it.
1412+
let stdout = git.smartlog()?;
1413+
insta::assert_snapshot!(stdout, @r###"
1414+
:
1415+
@ 62fc20d (> master) create test1.txt
1416+
|
1417+
o dc158fa empty commit 1
1418+
"###);
1419+
}
1420+
1421+
Ok(())
1422+
}
1423+
1424+
#[test]
1425+
fn test_record_new_with_create() -> eyre::Result<()> {
1426+
let git = make_git()?;
1427+
if !git.supports_reference_transactions()? {
1428+
return Ok(());
1429+
}
1430+
git.init_repo()?;
1431+
git.commit_file("test1", 1)?;
1432+
1433+
let stdout = git.smartlog()?;
1434+
insta::assert_snapshot!(stdout, @r###"
1435+
:
1436+
@ 62fc20d (> master) create test1.txt
1437+
"###);
1438+
1439+
{
1440+
// Create a new commit and a new branch, which should be checked out.
1441+
git.branchless(
1442+
"record",
1443+
&["-m", "empty commit 1", "--new", "--create", "foo"],
1444+
)?;
1445+
1446+
let stdout = git.smartlog()?;
1447+
insta::assert_snapshot!(stdout, @r###"
1448+
:
1449+
O 62fc20d (master) create test1.txt
1450+
|
1451+
@ dc158fa (> foo) empty commit 1
1452+
"###);
1453+
}
1454+
1455+
Ok(())
1456+
}

0 commit comments

Comments
 (0)