Skip to content

Commit 8688ee0

Browse files
committed
feat: gix index entries also prints attributes.
1 parent ea0bb64 commit 8688ee0

File tree

4 files changed

+178
-78
lines changed

4 files changed

+178
-78
lines changed
Lines changed: 162 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,175 @@
1-
pub fn entries(repo: gix::Repository, mut out: impl std::io::Write, format: crate::OutputFormat) -> anyhow::Result<()> {
2-
use crate::OutputFormat::*;
3-
let index = repo.index()?;
1+
#[derive(Debug)]
2+
pub struct Options {
3+
pub format: crate::OutputFormat,
4+
/// If true, also show attributes
5+
pub attributes: bool,
6+
}
7+
pub(crate) mod function {
8+
use crate::repository::index::entries::Options;
9+
use gix::attrs::State;
10+
use gix::bstr::ByteSlice;
11+
use gix::odb::FindExt;
12+
use std::borrow::Cow;
13+
use std::io::BufWriter;
414

5-
#[cfg(feature = "serde")]
6-
if let Json = format {
7-
out.write_all(b"[\n")?;
8-
}
15+
pub fn entries(
16+
repo: gix::Repository,
17+
mut out: impl std::io::Write,
18+
Options { format, attributes }: Options,
19+
) -> anyhow::Result<()> {
20+
use crate::OutputFormat::*;
21+
let index = repo.index()?;
22+
let mut cache = attributes
23+
.then(|| {
24+
repo.attributes(
25+
&index,
26+
gix::worktree::cache::state::attributes::Source::WorktreeThenIdMapping,
27+
None,
28+
)
29+
.map(|cache| (cache.attribute_matches(), cache))
30+
})
31+
.transpose()?;
932

10-
let mut entries = index.entries().iter().peekable();
11-
while let Some(entry) = entries.next() {
12-
match format {
13-
Human => to_human(&mut out, &index, entry)?,
14-
#[cfg(feature = "serde")]
15-
Json => to_json(&mut out, &index, entry, entries.peek().is_none())?,
33+
#[cfg(feature = "serde")]
34+
if let Json = format {
35+
out.write_all(b"[\n")?;
1636
}
17-
}
1837

19-
#[cfg(feature = "serde")]
20-
if let Json = format {
21-
out.write_all(b"]\n")?;
22-
}
23-
Ok(())
24-
}
38+
let mut out = BufWriter::new(out);
39+
let mut entries = index.entries().iter().peekable();
40+
while let Some(entry) = entries.next() {
41+
let attrs = cache
42+
.as_mut()
43+
.map(|(attrs, cache)| {
44+
cache
45+
.at_entry(entry.path(&index), None, |id, buf| repo.objects.find_blob(id, buf))
46+
.map(|entry| Attrs {
47+
is_excluded: entry.is_excluded(),
48+
attributes: {
49+
entry.matching_attributes(attrs);
50+
attrs.iter().map(|m| m.assignment.to_owned()).collect()
51+
},
52+
})
53+
})
54+
.transpose()?;
55+
match format {
56+
Human => to_human(&mut out, &index, entry, attrs)?,
57+
#[cfg(feature = "serde")]
58+
Json => to_json(&mut out, &index, entry, attrs, entries.peek().is_none())?,
59+
}
60+
}
2561

26-
#[cfg(feature = "serde")]
27-
pub(crate) fn to_json(
28-
mut out: &mut impl std::io::Write,
29-
index: &gix::index::File,
30-
entry: &gix::index::Entry,
31-
is_last: bool,
32-
) -> anyhow::Result<()> {
33-
use gix::bstr::ByteSlice;
62+
#[cfg(feature = "serde")]
63+
if let Json = format {
64+
use std::io::Write;
65+
out.write_all(b"]\n")?;
66+
}
67+
Ok(())
68+
}
3469

3570
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36-
struct Entry<'a> {
37-
stat: &'a gix::index::entry::Stat,
38-
hex_id: String,
39-
flags: u32,
40-
mode: u32,
41-
path: std::borrow::Cow<'a, str>,
71+
struct Attrs {
72+
is_excluded: bool,
73+
attributes: Vec<gix::attrs::Assignment>,
4274
}
4375

44-
serde_json::to_writer(
45-
&mut out,
46-
&Entry {
47-
stat: &entry.stat,
48-
hex_id: entry.id.to_hex().to_string(),
49-
flags: entry.flags.bits(),
50-
mode: entry.mode.bits(),
51-
path: entry.path(index).to_str_lossy(),
52-
},
53-
)?;
76+
#[cfg(feature = "serde")]
77+
fn to_json(
78+
mut out: &mut impl std::io::Write,
79+
index: &gix::index::File,
80+
entry: &gix::index::Entry,
81+
attrs: Option<Attrs>,
82+
is_last: bool,
83+
) -> anyhow::Result<()> {
84+
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
85+
struct Entry<'a> {
86+
stat: &'a gix::index::entry::Stat,
87+
hex_id: String,
88+
flags: u32,
89+
mode: u32,
90+
path: std::borrow::Cow<'a, str>,
91+
meta: Option<Attrs>,
92+
}
5493

55-
if is_last {
56-
out.write_all(b"\n")?;
57-
} else {
58-
out.write_all(b",\n")?;
59-
}
60-
Ok(())
61-
}
94+
serde_json::to_writer(
95+
&mut out,
96+
&Entry {
97+
stat: &entry.stat,
98+
hex_id: entry.id.to_hex().to_string(),
99+
flags: entry.flags.bits(),
100+
mode: entry.mode.bits(),
101+
path: entry.path(index).to_str_lossy(),
102+
meta: attrs,
103+
},
104+
)?;
62105

63-
pub(crate) fn to_human(
64-
out: &mut impl std::io::Write,
65-
file: &gix::index::File,
66-
entry: &gix::index::Entry,
67-
) -> std::io::Result<()> {
68-
writeln!(
69-
out,
70-
"{} {}{:?} {} {}",
71-
match entry.flags.stage() {
72-
0 => "BASE ",
73-
1 => "OURS ",
74-
2 => "THEIRS ",
75-
_ => "UNKNOWN",
76-
},
77-
if entry.flags.is_empty() {
78-
"".to_string()
106+
if is_last {
107+
out.write_all(b"\n")?;
79108
} else {
80-
format!("{:?} ", entry.flags)
81-
},
82-
entry.mode,
83-
entry.id,
84-
entry.path(file)
85-
)
109+
out.write_all(b",\n")?;
110+
}
111+
Ok(())
112+
}
113+
114+
fn to_human(
115+
out: &mut impl std::io::Write,
116+
file: &gix::index::File,
117+
entry: &gix::index::Entry,
118+
attrs: Option<Attrs>,
119+
) -> std::io::Result<()> {
120+
writeln!(
121+
out,
122+
"{} {}{:?} {} {}{}",
123+
match entry.flags.stage() {
124+
0 => "BASE ",
125+
1 => "OURS ",
126+
2 => "THEIRS ",
127+
_ => "UNKNOWN",
128+
},
129+
if entry.flags.is_empty() {
130+
"".to_string()
131+
} else {
132+
format!("{:?} ", entry.flags)
133+
},
134+
entry.mode,
135+
entry.id,
136+
entry.path(file),
137+
attrs
138+
.map(|a| {
139+
let mut buf = String::new();
140+
if a.is_excluded {
141+
buf.push_str(" ❌");
142+
}
143+
if !a.attributes.is_empty() {
144+
buf.push_str(" (");
145+
for assignment in a.attributes {
146+
match assignment.state {
147+
State::Set => {
148+
buf.push_str(assignment.name.as_str());
149+
}
150+
State::Unset => {
151+
buf.push('-');
152+
buf.push_str(assignment.name.as_str());
153+
}
154+
State::Value(v) => {
155+
buf.push_str(assignment.name.as_str());
156+
buf.push('=');
157+
buf.push_str(v.as_ref().as_bstr().to_str_lossy().as_ref());
158+
}
159+
State::Unspecified => {
160+
buf.push('!');
161+
buf.push_str(assignment.name.as_str());
162+
}
163+
}
164+
buf.push_str(", ");
165+
}
166+
buf.pop();
167+
buf.pop();
168+
buf.push(')');
169+
}
170+
buf.into()
171+
})
172+
.unwrap_or(Cow::Borrowed(""))
173+
)
174+
}
86175
}

gitoxide-core/src/repository/index/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ pub fn from_tree(
3535
Ok(())
3636
}
3737

38-
mod entries;
39-
pub use entries::entries;
38+
pub mod entries;
39+
pub use entries::function::entries;

src/plumbing/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -855,14 +855,21 @@ pub fn main() -> Result<()> {
855855
),
856856
},
857857
Subcommands::Index(cmd) => match cmd {
858-
index::Subcommands::Entries => prepare_and_run(
858+
index::Subcommands::Entries { no_attributes } => prepare_and_run(
859859
"index-entries",
860860
verbose,
861861
progress,
862862
progress_keep_open,
863863
None,
864864
move |_progress, out, _err| {
865-
core::repository::index::entries(repository(Mode::LenientWithGitInstallConfig)?, out, format)
865+
core::repository::index::entries(
866+
repository(Mode::LenientWithGitInstallConfig)?,
867+
out,
868+
core::repository::index::entries::Options {
869+
format,
870+
attributes: !no_attributes,
871+
},
872+
)
866873
},
867874
),
868875
index::Subcommands::FromTree {

src/plumbing/options/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,11 @@ pub mod index {
472472
#[derive(Debug, clap::Subcommand)]
473473
pub enum Subcommands {
474474
/// Print all entries to standard output
475-
Entries,
475+
Entries {
476+
/// Do not visualize excluded entries or attributes per path.
477+
#[clap(long)]
478+
no_attributes: bool,
479+
},
476480
/// Create an index from a tree-ish.
477481
#[clap(visible_alias = "read-tree")]
478482
FromTree {

0 commit comments

Comments
 (0)