git2/
reflog.rs

1use libc::size_t;
2use std::iter::FusedIterator;
3use std::marker;
4use std::ops::Range;
5use std::str;
6
7use crate::util::Binding;
8use crate::{raw, signature, Error, Oid, Signature};
9
10/// A reference log of a git repository.
11pub struct Reflog {
12    raw: *mut raw::git_reflog,
13}
14
15/// An entry inside the reflog of a repository
16pub struct ReflogEntry<'reflog> {
17    raw: *const raw::git_reflog_entry,
18    _marker: marker::PhantomData<&'reflog Reflog>,
19}
20
21/// An iterator over the entries inside of a reflog.
22pub struct ReflogIter<'reflog> {
23    range: Range<usize>,
24    reflog: &'reflog Reflog,
25}
26
27impl Reflog {
28    /// Add a new entry to the in-memory reflog.
29    pub fn append(
30        &mut self,
31        new_oid: Oid,
32        committer: &Signature<'_>,
33        msg: Option<&str>,
34    ) -> Result<(), Error> {
35        let msg = crate::opt_cstr(msg)?;
36        unsafe {
37            try_call!(raw::git_reflog_append(
38                self.raw,
39                new_oid.raw(),
40                committer.raw(),
41                msg
42            ));
43        }
44        Ok(())
45    }
46
47    /// Remove an entry from the reflog by its index
48    ///
49    /// To ensure there's no gap in the log history, set rewrite_previous_entry
50    /// param value to `true`. When deleting entry n, member old_oid of entry
51    /// n-1 (if any) will be updated with the value of member new_oid of entry
52    /// n+1.
53    pub fn remove(&mut self, i: usize, rewrite_previous_entry: bool) -> Result<(), Error> {
54        unsafe {
55            try_call!(raw::git_reflog_drop(
56                self.raw,
57                i as size_t,
58                rewrite_previous_entry
59            ));
60        }
61        Ok(())
62    }
63
64    /// Lookup an entry by its index
65    ///
66    /// Requesting the reflog entry with an index of 0 (zero) will return the
67    /// most recently created entry.
68    pub fn get(&self, i: usize) -> Option<ReflogEntry<'_>> {
69        unsafe {
70            let ptr = raw::git_reflog_entry_byindex(self.raw, i as size_t);
71            Binding::from_raw_opt(ptr)
72        }
73    }
74
75    /// Get the number of log entries in a reflog
76    pub fn len(&self) -> usize {
77        unsafe { raw::git_reflog_entrycount(self.raw) as usize }
78    }
79
80    /// Return `true ` is there is no log entry in a reflog
81    pub fn is_empty(&self) -> bool {
82        self.len() == 0
83    }
84
85    /// Get an iterator to all entries inside of this reflog
86    pub fn iter(&self) -> ReflogIter<'_> {
87        ReflogIter {
88            range: 0..self.len(),
89            reflog: self,
90        }
91    }
92
93    /// Write an existing in-memory reflog object back to disk using an atomic
94    /// file lock.
95    pub fn write(&mut self) -> Result<(), Error> {
96        unsafe {
97            try_call!(raw::git_reflog_write(self.raw));
98        }
99        Ok(())
100    }
101}
102
103impl Binding for Reflog {
104    type Raw = *mut raw::git_reflog;
105
106    unsafe fn from_raw(raw: *mut raw::git_reflog) -> Reflog {
107        Reflog { raw }
108    }
109    fn raw(&self) -> *mut raw::git_reflog {
110        self.raw
111    }
112}
113
114impl Drop for Reflog {
115    fn drop(&mut self) {
116        unsafe { raw::git_reflog_free(self.raw) }
117    }
118}
119
120impl<'reflog> ReflogEntry<'reflog> {
121    /// Get the committer of this entry
122    pub fn committer(&self) -> Signature<'_> {
123        unsafe {
124            let ptr = raw::git_reflog_entry_committer(self.raw);
125            signature::from_raw_const(self, ptr)
126        }
127    }
128
129    /// Get the new oid
130    pub fn id_new(&self) -> Oid {
131        unsafe { Binding::from_raw(raw::git_reflog_entry_id_new(self.raw)) }
132    }
133
134    /// Get the old oid
135    pub fn id_old(&self) -> Oid {
136        unsafe { Binding::from_raw(raw::git_reflog_entry_id_old(self.raw)) }
137    }
138
139    /// Get the log message, returning `None` on invalid UTF-8.
140    pub fn message(&self) -> Option<&str> {
141        self.message_bytes().and_then(|s| str::from_utf8(s).ok())
142    }
143
144    /// Get the log message as a byte array.
145    pub fn message_bytes(&self) -> Option<&[u8]> {
146        unsafe { crate::opt_bytes(self, raw::git_reflog_entry_message(self.raw)) }
147    }
148}
149
150impl<'reflog> Binding for ReflogEntry<'reflog> {
151    type Raw = *const raw::git_reflog_entry;
152
153    unsafe fn from_raw(raw: *const raw::git_reflog_entry) -> ReflogEntry<'reflog> {
154        ReflogEntry {
155            raw,
156            _marker: marker::PhantomData,
157        }
158    }
159    fn raw(&self) -> *const raw::git_reflog_entry {
160        self.raw
161    }
162}
163
164impl<'reflog> Iterator for ReflogIter<'reflog> {
165    type Item = ReflogEntry<'reflog>;
166    fn next(&mut self) -> Option<ReflogEntry<'reflog>> {
167        self.range.next().and_then(|i| self.reflog.get(i))
168    }
169    fn size_hint(&self) -> (usize, Option<usize>) {
170        self.range.size_hint()
171    }
172}
173impl<'reflog> DoubleEndedIterator for ReflogIter<'reflog> {
174    fn next_back(&mut self) -> Option<ReflogEntry<'reflog>> {
175        self.range.next_back().and_then(|i| self.reflog.get(i))
176    }
177}
178impl<'reflog> FusedIterator for ReflogIter<'reflog> {}
179impl<'reflog> ExactSizeIterator for ReflogIter<'reflog> {}
180
181#[cfg(test)]
182mod tests {
183    #[test]
184    fn smoke() {
185        let (_td, repo) = crate::test::repo_init();
186        let mut reflog = repo.reflog("HEAD").unwrap();
187        assert_eq!(reflog.iter().len(), 1);
188        reflog.write().unwrap();
189
190        let entry = reflog.iter().next().unwrap();
191        assert!(entry.message().is_some());
192
193        repo.reflog_rename("HEAD", "refs/heads/foo").unwrap();
194        repo.reflog_delete("refs/heads/foo").unwrap();
195    }
196}