Struct git_repository::Commit
source · Expand description
A decoded commit object with access to its owning repository.
Fields§
§id: ObjectId
The id of the commit
data: Vec<u8>
The fully decoded commit data
Implementations§
source§impl<'repo> Commit<'repo>
impl<'repo> Commit<'repo>
sourcepub fn detached(&self) -> ObjectDetached
pub fn detached(&self) -> ObjectDetached
Create an owned instance of this object, copying our data in the process.
sourcepub fn detach(self) -> ObjectDetached
pub fn detach(self) -> ObjectDetached
Sever the connection to the Repository
and turn this instance into a standalone object.
source§impl<'repo> Commit<'repo>
impl<'repo> Commit<'repo>
sourcepub fn short_id(&self) -> Result<Prefix, Error>
pub fn short_id(&self) -> Result<Prefix, Error>
Turn this objects id into a shortened id with a length in hex as configured by core.abbrev
.
sourcepub fn message(&self) -> Result<MessageRef<'_>, Error>
pub fn message(&self) -> Result<MessageRef<'_>, Error>
Parse the commits message into a MessageRef
sourcepub fn message_raw(&self) -> Result<&BStr, Error>
pub fn message_raw(&self) -> Result<&BStr, Error>
Decode the commit object until the message and return it.
sourcepub fn message_raw_sloppy(&self) -> &BStr
pub fn message_raw_sloppy(&self) -> &BStr
Obtain the message by using intricate knowledge about the encoding, which is fastest and can’t fail at the expense of error handling.
Examples found in repository?
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
fn find(&mut self, regex: &BStr, negated: bool) -> Option<()> {
self.unset_disambiguate_call();
self.follow_refs_to_objects_if_needed()?;
#[cfg(not(feature = "regex"))]
let matches = |message: &BStr| -> bool { message.contains_str(regex) ^ negated };
#[cfg(feature = "regex")]
let matches = match regex::bytes::Regex::new(regex.to_str_lossy().as_ref()) {
Ok(compiled) => {
let needs_regex = regex::escape(compiled.as_str()) != regex;
move |message: &BStr| -> bool {
if needs_regex {
compiled.is_match(message) ^ negated
} else {
message.contains_str(regex) ^ negated
}
}
}
Err(err) => {
self.err.push(err.into());
return None;
}
};
match self.objs[self.idx].as_mut() {
Some(objs) => {
let repo = self.repo;
let mut errors = Vec::new();
let mut replacements = Replacements::default();
for oid in objs.iter() {
match oid
.attach(repo)
.ancestors()
.sorting(Sorting::ByCommitTimeNewestFirst)
.all()
{
Ok(iter) => {
let mut matched = false;
let mut count = 0;
let commits = iter.map(|res| {
res.map_err(Error::from).and_then(|commit_id| {
commit_id.object().map_err(Error::from).map(|obj| obj.into_commit())
})
});
for commit in commits {
count += 1;
match commit {
Ok(commit) => {
if matches(commit.message_raw_sloppy()) {
replacements.push((*oid, commit.id));
matched = true;
break;
}
}
Err(err) => errors.push((*oid, err)),
}
}
if !matched {
errors.push((
*oid,
Error::NoRegexMatch {
regex: regex.into(),
commits_searched: count,
oid: oid.attach(repo).shorten_or_id(),
},
))
}
}
Err(err) => errors.push((*oid, err.into())),
}
}
handle_errors_and_replacements(&mut self.err, objs, errors, &mut replacements)
}
None => match self.repo.references() {
Ok(references) => match references.all() {
Ok(references) => {
match self
.repo
.rev_walk(
references
.peeled()
.filter_map(Result::ok)
.filter(|r| {
r.id()
.object()
.ok()
.map(|obj| obj.kind == git_object::Kind::Commit)
.unwrap_or(false)
})
.filter_map(|r| r.detach().peeled),
)
.sorting(Sorting::ByCommitTimeNewestFirst)
.all()
{
Ok(iter) => {
let mut matched = false;
let mut count = 0;
let commits = iter.map(|res| {
res.map_err(Error::from).and_then(|commit_id| {
commit_id.object().map_err(Error::from).map(|obj| obj.into_commit())
})
});
for commit in commits {
count += 1;
match commit {
Ok(commit) => {
if matches(commit.message_raw_sloppy()) {
self.objs[self.idx]
.get_or_insert_with(HashSet::default)
.insert(commit.id);
matched = true;
break;
}
}
Err(err) => self.err.push(err),
}
}
if matched {
Some(())
} else {
self.err.push(Error::NoRegexMatchAllRefs {
regex: regex.into(),
commits_searched: count,
});
None
}
}
Err(err) => {
self.err.push(err.into());
None
}
}
}
Err(err) => {
self.err.push(err.into());
None
}
},
Err(err) => {
self.err.push(err.into());
None
}
},
}
}
sourcepub fn time(&self) -> Result<Time, Error>
pub fn time(&self) -> Result<Time, Error>
Decode the commit and obtain the time at which the commit was created.
For the time at which it was authored, refer to .decode()?.author.time
.
sourcepub fn decode(&self) -> Result<CommitRef<'_>, Error>
pub fn decode(&self) -> Result<CommitRef<'_>, Error>
Decode the entire commit object and return it for accessing all commit information.
It will allocate only if there are more than 2 parents.
Note that the returned commit object does make lookup easy and should be used for successive calls to string-ish information to avoid decoding the object more than once.
sourcepub fn iter(&self) -> CommitRefIter<'_>
pub fn iter(&self) -> CommitRefIter<'_>
Return an iterator over tokens, representing this commit piece by piece.
Return the commits author, with surrounding whitespace trimmed.
sourcepub fn committer(&self) -> Result<SignatureRef<'_>, Error>
pub fn committer(&self) -> Result<SignatureRef<'_>, Error>
Return the commits committer. with surrounding whitespace trimmed.
Examples found in repository?
More examples
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
pub(crate) fn update(
repo: &Repository,
message: RefLogMessage,
mappings: &[fetch::Mapping],
refspecs: &[git_refspec::RefSpec],
extra_refspecs: &[git_refspec::RefSpec],
fetch_tags: fetch::Tags,
dry_run: fetch::DryRun,
write_packed_refs: fetch::WritePackedRefs,
) -> Result<update::Outcome, update::Error> {
let mut edits = Vec::new();
let mut updates = Vec::new();
let implicit_tag_refspec = fetch_tags
.to_refspec()
.filter(|_| matches!(fetch_tags, crate::remote::fetch::Tags::Included));
for (remote, local, spec, is_implicit_tag) in mappings.iter().filter_map(
|fetch::Mapping {
remote,
local,
spec_index,
}| {
spec_index.get(refspecs, extra_refspecs).map(|spec| {
(
remote,
local,
spec,
implicit_tag_refspec.map_or(false, |tag_spec| spec.to_ref() == tag_spec),
)
})
},
) {
let remote_id = match remote.as_id() {
Some(id) => id,
None => continue,
};
if dry_run == fetch::DryRun::No && !repo.objects.contains(remote_id) {
let update = if is_implicit_tag {
update::Mode::ImplicitTagNotSentByRemote.into()
} else {
update::Mode::RejectedSourceObjectNotFound { id: remote_id.into() }.into()
};
updates.push(update);
continue;
}
let checked_out_branches = worktree_branches(repo)?;
let (mode, edit_index) = match local {
Some(name) => {
let (mode, reflog_message, name, previous_value) = match repo.try_find_reference(name)? {
Some(existing) => {
if let Some(wt_dir) = checked_out_branches.get(existing.name()) {
let mode = update::Mode::RejectedCurrentlyCheckedOut {
worktree_dir: wt_dir.to_owned(),
};
updates.push(mode.into());
continue;
}
match existing.target() {
TargetRef::Symbolic(_) => {
updates.push(update::Mode::RejectedSymbolic.into());
continue;
}
TargetRef::Peeled(local_id) => {
let previous_value =
PreviousValue::MustExistAndMatch(Target::Peeled(local_id.to_owned()));
let (mode, reflog_message) = if local_id == remote_id {
(update::Mode::NoChangeNeeded, "no update will be performed")
} else if let Some(git_ref::Category::Tag) = existing.name().category() {
if spec.allow_non_fast_forward() {
(update::Mode::Forced, "updating tag")
} else {
updates.push(update::Mode::RejectedTagUpdate.into());
continue;
}
} else {
let mut force = spec.allow_non_fast_forward();
let is_fast_forward = match dry_run {
fetch::DryRun::No => {
let ancestors = repo
.find_object(local_id)?
.try_into_commit()
.map_err(|_| ())
.and_then(|c| {
c.committer().map(|a| a.time.seconds_since_unix_epoch).map_err(|_| ())
}).and_then(|local_commit_time|
remote_id
.to_owned()
.ancestors(|id, buf| repo.objects.find_commit_iter(id, buf))
.sorting(
git_traverse::commit::Sorting::ByCommitTimeNewestFirstCutoffOlderThan {
time_in_seconds_since_epoch: local_commit_time
},
)
.map_err(|_| ())
);
match ancestors {
Ok(mut ancestors) => {
ancestors.any(|cid| cid.map_or(false, |cid| cid == local_id))
}
Err(_) => {
force = true;
false
}
}
}
fetch::DryRun::Yes => true,
};
if is_fast_forward {
(
update::Mode::FastForward,
matches!(dry_run, fetch::DryRun::Yes)
.then(|| "fast-forward (guessed in dry-run)")
.unwrap_or("fast-forward"),
)
} else if force {
(update::Mode::Forced, "forced-update")
} else {
updates.push(update::Mode::RejectedNonFastForward.into());
continue;
}
};
(mode, reflog_message, existing.name().to_owned(), previous_value)
}
}
}
None => {
let name: git_ref::FullName = name.try_into()?;
let reflog_msg = match name.category() {
Some(git_ref::Category::Tag) => "storing tag",
Some(git_ref::Category::LocalBranch) => "storing head",
_ => "storing ref",
};
(
update::Mode::New,
reflog_msg,
name,
PreviousValue::ExistingMustMatch(Target::Peeled(remote_id.to_owned())),
)
}
};
let edit = RefEdit {
change: Change::Update {
log: LogChange {
mode: RefLog::AndReference,
force_create_reflog: false,
message: message.compose(reflog_message),
},
expected: previous_value,
new: if let Source::Ref(git_protocol::handshake::Ref::Symbolic { target, .. }) = &remote {
match mappings.iter().find_map(|m| {
m.remote.as_name().and_then(|name| {
(name == target)
.then(|| m.local.as_ref().and_then(|local| local.try_into().ok()))
.flatten()
})
}) {
Some(local_branch) => {
// This is always safe because…
// - the reference may exist already
// - if it doesn't exist it will be created - we are here because it's in the list of mappings after all
// - if it exists and is updated, and the update is rejected due to non-fastforward for instance, the
// target reference still exists and we can point to it.
Target::Symbolic(local_branch)
}
None => Target::Peeled(remote_id.into()),
}
} else {
Target::Peeled(remote_id.into())
},
},
name,
deref: false,
};
let edit_index = edits.len();
edits.push(edit);
(mode, Some(edit_index))
}
None => (update::Mode::NoChangeNeeded, None),
};
updates.push(Update { mode, edit_index })
}
let edits = match dry_run {
fetch::DryRun::No => {
let (file_lock_fail, packed_refs_lock_fail) = repo
.config
.lock_timeout()
.map_err(crate::reference::edit::Error::from)?;
repo.refs
.transaction()
.packed_refs(
match write_packed_refs {
fetch::WritePackedRefs::Only => {
git_ref::file::transaction::PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(Box::new(|oid, buf| {
repo.objects
.try_find(oid, buf)
.map(|obj| obj.map(|obj| obj.kind))
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)
}))},
fetch::WritePackedRefs::Never => git_ref::file::transaction::PackedRefs::DeletionsOnly
}
)
.prepare(edits, file_lock_fail, packed_refs_lock_fail)
.map_err(crate::reference::edit::Error::from)?
.commit(repo.committer_or_default())
.map_err(crate::reference::edit::Error::from)?
}
fetch::DryRun::Yes => edits,
};
Ok(update::Outcome { edits, updates })
}
sourcepub fn parent_ids(&self) -> impl Iterator<Item = Id<'repo>> + '_
pub fn parent_ids(&self) -> impl Iterator<Item = Id<'repo>> + '_
Decode this commits parent ids on the fly without allocating.
Examples found in repository?
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
fn traverse(&mut self, kind: Traversal) -> Option<()> {
self.unset_disambiguate_call();
self.follow_refs_to_objects_if_needed()?;
let mut replacements = Replacements::default();
let mut errors = Vec::new();
let objs = self.objs[self.idx].as_mut()?;
let repo = self.repo;
for obj in objs.iter() {
match kind {
Traversal::NthParent(num) => {
match self.repo.find_object(*obj).map_err(Error::from).and_then(|obj| {
obj.try_into_commit().map_err(|err| {
let object::try_into::Error { actual, expected, id } = err;
Error::ObjectKind {
oid: id.attach(repo).shorten_or_id(),
actual,
expected,
}
})
}) {
Ok(commit) => match commit.parent_ids().nth(num.saturating_sub(1)) {
Some(id) => replacements.push((commit.id, id.detach())),
None => errors.push((
commit.id,
Error::ParentOutOfRange {
oid: commit.id().shorten_or_id(),
desired: num,
available: commit.parent_ids().count(),
},
)),
},
Err(err) => errors.push((*obj, err)),
}
}
Traversal::NthAncestor(num) => {
let id = obj.attach(repo);
match id
.ancestors()
.first_parent_only()
.all()
.expect("cannot fail without sorting")
.skip(num)
.filter_map(Result::ok)
.next()
{
Some(id) => replacements.push((*obj, id.detach())),
None => errors.push((
*obj,
Error::AncestorOutOfRange {
oid: id.shorten_or_id(),
desired: num,
available: id
.ancestors()
.first_parent_only()
.all()
.expect("cannot fail without sorting")
.skip(1)
.count(),
},
)),
}
}
}
}
handle_errors_and_replacements(&mut self.err, objs, errors, &mut replacements)
}
sourcepub fn tree(&self) -> Result<Tree<'repo>, Error>
pub fn tree(&self) -> Result<Tree<'repo>, Error>
Parse the commit and return the the tree object it points to.
Examples found in repository?
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
pub fn open_mailmap_into(&self, target: &mut git_mailmap::Snapshot) -> Result<(), crate::mailmap::load::Error> {
let mut err = None::<crate::mailmap::load::Error>;
let mut buf = Vec::new();
let mut blob_id = self
.config
.resolved
.raw_value("mailmap", None, "blob")
.ok()
.and_then(|spec| {
// TODO: actually resolve this as spec (once we can do that)
git_hash::ObjectId::from_hex(spec.as_ref())
.map_err(|e| err.get_or_insert(e.into()))
.ok()
});
match self.work_dir() {
None => {
// TODO: replace with ref-spec `HEAD:.mailmap` for less verbose way of getting the blob id
blob_id = blob_id.or_else(|| {
self.head().ok().and_then(|mut head| {
let commit = head.peel_to_commit_in_place().ok()?;
let tree = commit.tree().ok()?;
tree.lookup_entry(Some(".mailmap")).ok()?.map(|e| e.object_id())
})
});
}
Some(root) => {
if let Ok(mut file) = git_features::fs::open_options_no_follow()
.read(true)
.open(root.join(".mailmap"))
.map_err(|e| {
if e.kind() != std::io::ErrorKind::NotFound {
err.get_or_insert(e.into());
}
})
{
buf.clear();
std::io::copy(&mut file, &mut buf)
.map_err(|e| err.get_or_insert(e.into()))
.ok();
target.merge(git_mailmap::parse_ignore_errors(&buf));
}
}
}
if let Some(blob) = blob_id.and_then(|id| self.find_object(id).map_err(|e| err.get_or_insert(e.into())).ok()) {
target.merge(git_mailmap::parse_ignore_errors(&blob.data));
}
let configured_path = self
.config
.resolved
.value::<git_config::Path<'_>>("mailmap", None, "file")
.ok()
.and_then(|path| {
let install_dir = self.install_dir().ok()?;
let home = self.config.home_dir();
match path.interpolate(git_config::path::interpolate::Context {
git_install_dir: Some(install_dir.as_path()),
home_dir: home.as_deref(),
home_for_user: if self.options.git_dir_trust.expect("trust is set") == git_sec::Trust::Full {
Some(git_config::path::interpolate::home_for_user)
} else {
None
},
}) {
Ok(path) => Some(path),
Err(e) => {
err.get_or_insert(e.into());
None
}
}
});
if let Some(mut file) =
configured_path.and_then(|path| std::fs::File::open(path).map_err(|e| err.get_or_insert(e.into())).ok())
{
buf.clear();
std::io::copy(&mut file, &mut buf)
.map_err(|e| err.get_or_insert(e.into()))
.ok();
target.merge(git_mailmap::parse_ignore_errors(&buf));
}
err.map(Err).unwrap_or(Ok(()))
}
sourcepub fn tree_id(&self) -> Result<Id<'repo>, Error>
pub fn tree_id(&self) -> Result<Id<'repo>, Error>
Parse the commit and return the the tree id it points to.
sourcepub fn id(&self) -> Id<'repo>
pub fn id(&self) -> Id<'repo>
Return our id own id with connection to this repository.
Examples found in repository?
More examples
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
fn traverse(&mut self, kind: Traversal) -> Option<()> {
self.unset_disambiguate_call();
self.follow_refs_to_objects_if_needed()?;
let mut replacements = Replacements::default();
let mut errors = Vec::new();
let objs = self.objs[self.idx].as_mut()?;
let repo = self.repo;
for obj in objs.iter() {
match kind {
Traversal::NthParent(num) => {
match self.repo.find_object(*obj).map_err(Error::from).and_then(|obj| {
obj.try_into_commit().map_err(|err| {
let object::try_into::Error { actual, expected, id } = err;
Error::ObjectKind {
oid: id.attach(repo).shorten_or_id(),
actual,
expected,
}
})
}) {
Ok(commit) => match commit.parent_ids().nth(num.saturating_sub(1)) {
Some(id) => replacements.push((commit.id, id.detach())),
None => errors.push((
commit.id,
Error::ParentOutOfRange {
oid: commit.id().shorten_or_id(),
desired: num,
available: commit.parent_ids().count(),
},
)),
},
Err(err) => errors.push((*obj, err)),
}
}
Traversal::NthAncestor(num) => {
let id = obj.attach(repo);
match id
.ancestors()
.first_parent_only()
.all()
.expect("cannot fail without sorting")
.skip(num)
.filter_map(Result::ok)
.next()
{
Some(id) => replacements.push((*obj, id.detach())),
None => errors.push((
*obj,
Error::AncestorOutOfRange {
oid: id.shorten_or_id(),
desired: num,
available: id
.ancestors()
.first_parent_only()
.all()
.expect("cannot fail without sorting")
.skip(1)
.count(),
},
)),
}
}
}
}
handle_errors_and_replacements(&mut self.err, objs, errors, &mut replacements)
}