1 use std
::{convert::TryInto, path::Path}
;
3 use gix_actor
::{Sign, Signature, Time}
;
4 use gix_object
::bstr
::ByteSlice
;
8 use crate::{file::WriteReflog, FullNameRef}
;
10 type Result
<T
= ()> = std
::result
::Result
<T
, Box
<dyn std
::error
::Error
>>;
12 /// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_.
13 fn hex_to_id(hex
: &str) -> gix_hash
::ObjectId
{
14 gix_hash
::ObjectId
::from_hex(hex
.as_bytes()).expect("40 bytes hex")
17 fn empty_store(writemode
: WriteReflog
) -> Result
<(TempDir
, file
::Store
)> {
18 let dir
= TempDir
::new()?
;
19 let store
= file
::Store
::at(dir
.path(), writemode
, gix_hash
::Kind
::Sha1
);
23 fn reflog_lines(store
: &file
::Store
, name
: &str, buf
: &mut Vec
<u8>) -> Result
<Vec
<crate::log
::Line
>> {
25 .reflog_iter(name
, buf
)?
26 .expect("existing reflog")
27 .map(|l
| l
.map(crate::log
::Line
::from
))
28 .collect
::<std
::result
::Result
<Vec
<_
>, _
>>()
32 const WRITE_MODES
: &[WriteReflog
] = &[WriteReflog
::Normal
, WriteReflog
::Disable
, WriteReflog
::Always
];
35 fn should_autocreate_is_unaffected_by_writemode() -> Result
{
36 let (_keep
, store
) = empty_store(WriteReflog
::Disable
)?
;
37 for should_create_name
in &["HEAD", "refs/heads/main", "refs/remotes/any", "refs/notes/any"] {
38 assert
!(store
.should_autocreate_reflog(Path
::new(should_create_name
)));
40 for should_not_create_name
in &["FETCH_HEAD", "SOMETHING", "refs/special/this", "refs/tags/0.1.0"] {
41 assert
!(!store
.should_autocreate_reflog(Path
::new(should_not_create_name
)));
47 fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append_log_lines() -> Result
{
48 for mode
in WRITE_MODES
{
49 let (_keep
, store
) = empty_store(*mode
)?
;
50 let full_name_str
= "refs/heads/main";
51 let full_name
: &FullNameRef
= full_name_str
.try_into()?
;
52 let new
= hex_to_id("28ce6a8b26aa170e1de65536fe8abe1832bd3242");
53 let committer
= Signature
{
54 name
: "committer".into(),
55 email
: "committer@example.com".into(),
57 seconds_since_unix_epoch
: 1234,
58 offset_in_seconds
: 1800,
62 store
.reflog_create_or_append(
66 committer
.to_ref().into(),
67 b
"the message".as_bstr(),
71 let mut buf
= Vec
::new();
73 WriteReflog
::Normal
| WriteReflog
::Always
=> {
75 reflog_lines(&store
, full_name_str
, &mut buf
)?
,
76 vec
![crate::log
::Line
{
77 previous_oid
: gix_hash
::Kind
::Sha1
.null(),
79 signature
: committer
.clone(),
80 message
: "the message".into()
83 let previous
= hex_to_id("0000000000000000000000111111111111111111");
84 store
.reflog_create_or_append(
88 committer
.to_ref().into(),
89 b
"next message".as_bstr(),
93 let lines
= reflog_lines(&store
, full_name_str
, &mut buf
)?
;
94 assert_eq
!(lines
.len(), 2, "now there is another line");
96 lines
.last().expect("non-empty"),
98 previous_oid
: previous
,
100 signature
: committer
.clone(),
101 message
: "next message".into()
105 WriteReflog
::Disable
=> {
107 store
.reflog_iter(full_name
, &mut buf
)?
.is_none(),
108 "there is no logs in disabled mode"
113 // create onto existing directory
114 let full_name_str
= "refs/heads/other";
115 let full_name
: &FullNameRef
= full_name_str
.try_into()?
;
116 let reflog_path
= store
.reflog_path(full_name_str
.try_into().expect("valid"));
117 let directory_in_place_of_reflog
= reflog_path
.join("empty-a").join("empty-b");
118 std
::fs
::create_dir_all(directory_in_place_of_reflog
)?
;
120 store
.reflog_create_or_append(
124 committer
.to_ref().into(),
125 b
"more complicated reflog creation".as_bstr(),
130 WriteReflog
::Normal
| WriteReflog
::Always
=> {
132 reflog_lines(&store
, full_name_str
, &mut buf
)?
.len(),
134 "reflog was written despite directory"
137 reflog_path
.is_file(),
138 "the empty directory was replaced with the reflog file"
141 WriteReflog
::Disable
=> {
143 store
.reflog_iter(full_name_str
, &mut buf
)?
.is_none(),
144 "reflog still doesn't exist"
147 store
.reflog_iter_rev(full_name_str
, &mut buf
)?
.is_none(),
148 "reflog still doesn't exist"
150 assert
!(reflog_path
.is_dir(), "reflog directory wasn't touched");