git_zero_core/
checkout.rs1use crate::{
6 Error, Object, ObjectFormat, ObjectId, ObjectKind, Result,
7 object::{Commit, Tag, TreeEntry},
8};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum Mode {
13 Directory,
14 File,
15 Executable,
16 Symlink,
17 Gitlink,
18}
19
20impl Mode {
21 pub fn from_raw(mode: u32) -> Result<Self> {
22 match mode {
23 0o040000 => Ok(Self::Directory),
24 0o100644 => Ok(Self::File),
25 0o100755 => Ok(Self::Executable),
26 0o120000 => Ok(Self::Symlink),
27 0o160000 => Ok(Self::Gitlink),
28 _ => Err(Error::InvalidObject),
29 }
30 }
31
32 pub const fn raw(self) -> u32 {
33 match self {
34 Self::Directory => 0o040000,
35 Self::File => 0o100644,
36 Self::Executable => 0o100755,
37 Self::Symlink => 0o120000,
38 Self::Gitlink => 0o160000,
39 }
40 }
41
42 pub fn contents(self, object: Object<'_>) -> Result<&[u8]> {
46 if !matches!(self, Self::File | Self::Executable | Self::Symlink) {
47 return Err(Error::Unsupported);
48 }
49 if object.kind != ObjectKind::Blob || (self == Self::Symlink && object.data.contains(&0)) {
50 return Err(Error::InvalidObject);
51 }
52 Ok(object.data)
53 }
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub struct Entry {
59 pub id: ObjectId,
60 pub mode: Mode,
61}
62
63pub fn lookup(data: &[u8], format: ObjectFormat, name: &[u8]) -> Result<Option<Entry>> {
67 if name.is_empty() || matches!(name, b"." | b"..") || name.iter().any(|b| matches!(b, 0 | b'/'))
68 {
69 return Err(Error::InvalidFileName);
70 }
71 let mut offset = 0;
72 let mut found = None;
73 while let Some(entry) = TreeEntry::read_with_format(data, &mut offset, format)? {
74 if entry.name == name {
75 if found.is_some() {
76 return Err(Error::InvalidObject);
77 }
78 found = Some(Entry {
79 id: entry.id,
80 mode: Mode::from_raw(entry.mode)?,
81 });
82 }
83 }
84 Ok(found)
85}
86
87pub trait ObjectSource {
89 fn object(&mut self, id: ObjectId) -> Result<Object<'_>>;
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub struct Message {
95 pub id: ObjectId,
96 pub offset: usize,
97 pub length: usize,
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct Root {
102 pub entry: Entry,
103 pub message: Option<Message>,
104}
105
106pub fn resolve_root(
110 source: &mut impl ObjectSource,
111 mut id: ObjectId,
112 max_tags: u32,
113) -> Result<Root> {
114 let format = id.format();
115 let mut expected = None;
116 let mut message = None;
117 let mut tags = 0;
118 loop {
119 let object = source.object(id)?;
120 if expected.is_some_and(|kind| kind != object.kind) {
121 return Err(Error::InvalidObject);
122 }
123 match object.kind {
124 ObjectKind::Tag => {
125 if tags == max_tags {
126 return Err(Error::LimitExceeded);
127 }
128 let tag = Tag::parse(object.data)?;
129 if tag.target.format() != format {
130 return Err(Error::InvalidObjectId);
131 }
132 id = tag.target;
133 expected = Some(tag.target_kind);
134 tags += 1;
135 }
136 ObjectKind::Commit => {
137 let commit = Commit::parse_with_format(object.data, format)?;
138 message = Some(Message {
139 id,
140 offset: commit.message_offset(),
141 length: commit.message().len(),
142 });
143 id = commit.tree();
144 expected = Some(ObjectKind::Tree);
145 }
146 ObjectKind::Tree | ObjectKind::Blob => {
147 return Ok(Root {
148 entry: Entry {
149 id,
150 mode: if object.kind == ObjectKind::Tree {
151 Mode::Directory
152 } else {
153 Mode::File
154 },
155 },
156 message,
157 });
158 }
159 }
160 }
161}