1use git2::{self, Commit, Oid, Reference, References};
16use std::fmt;
17use std::hash;
18use std::result::Result as RResult;
19
20use error::*;
21use error::ErrorKind as EK;
22use iter::Messages;
23
24
25#[derive(PartialEq)]
26pub enum IssueRefType {
27 Any,
28 Head,
29 Leaf,
30}
31
32impl IssueRefType {
33 pub fn glob_part(&self) -> &'static str {
36 match *self {
37 IssueRefType::Any => "**",
38 IssueRefType::Head => "head",
39 IssueRefType::Leaf => "leaves/*",
40 }
41 }
42
43 pub fn of_ref(refname: &str) -> Option<(Oid, IssueRefType)> {
51 let mut parts = refname.rsplit('/');
52
53 let preliminary_ref_type = match parts.next() {
55 Some("head") => IssueRefType::Head,
56 Some(part) => if Self::id_from_str(part).is_some() {
57 match parts.next() {
60 Some("leaves") => IssueRefType::Leaf,
61 _ => return None,
62 }
63 } else {
64 return None
65 },
66 None => return None,
67 };
68
69 if let Some(id) = parts.next().and_then(Self::id_from_str) {
72 if parts.any(|part| part == "dit") {
75 return Some((id, preliminary_ref_type));
76 }
77 }
78
79 None
80 }
81
82 fn id_from_str(id: &str) -> Option<Oid> {
88 if id.len() == 40 {
89 Oid::from_str(id).ok()
90 } else {
91 None
92 }
93 }
94}
95
96impl fmt::Debug for IssueRefType {
97 fn fmt(&self, f: &mut fmt::Formatter) -> RResult<(), fmt::Error> {
98 f.write_str(match self {
99 &IssueRefType::Any => "Any ref",
100 &IssueRefType::Head => "Head ref",
101 &IssueRefType::Leaf => "Leaf ref",
102 })
103 }
104}
105
106
107pub struct Issue<'r> {
113 repo: &'r git2::Repository,
114 obj: git2::Object<'r>,
115}
116
117impl<'r> Issue<'r> {
118 pub fn new(repo: &'r git2::Repository, id: Oid) -> Result<Self> {
121 repo.find_object(id, Some(git2::ObjectType::Commit))
122 .chain_err(|| EK::CannotGetCommitForRev(id.to_string()))
123 .map(|obj| Issue { repo: repo, obj: obj })
124 }
125
126 pub fn id(&self) -> Oid {
129 self.obj.id()
130 }
131
132 pub fn initial_message(&self) -> Result<git2::Commit<'r>> {
135 self.obj
136 .clone()
137 .into_commit()
138 .map_err(|obj| Error::from_kind(EK::CannotGetCommitForRev(obj.id().to_string())))
139 }
140
141 pub fn heads(&self) -> Result<References<'r>> {
147 let glob = format!("**/dit/{}/head", self.ref_part());
148 self.repo
149 .references_glob(&glob)
150 .chain_err(|| EK::CannotFindIssueHead(self.id()))
151 }
152
153 pub fn local_head(&self) -> Result<Reference<'r>> {
159 let refname = format!("refs/dit/{}/head", self.ref_part());
160 self.repo
161 .find_reference(&refname)
162 .chain_err(|| EK::CannotFindIssueHead(self.id()))
163 }
164
165 pub fn local_refs(&self, ref_type: IssueRefType) -> Result<References<'r>> {
171 let glob = format!("refs/dit/{}/{}", self.ref_part(), ref_type.glob_part());
172 self.repo
173 .references_glob(&glob)
174 .chain_err(|| EK::CannotGetReferences(glob))
175 }
176
177 pub fn remote_refs(&self, ref_type: IssueRefType) -> Result<References<'r>> {
183 let glob = format!("refs/remotes/*/dit/{}/{}", self.ref_part(), ref_type.glob_part());
184 self.repo
185 .references_glob(&glob)
186 .chain_err(|| EK::CannotGetReferences(glob))
187 }
188
189 pub fn all_refs(&self, ref_type: IssueRefType) -> Result<References<'r>> {
195 let glob = format!("**/dit/{}/{}", self.ref_part(), ref_type.glob_part());
196 self.repo
197 .references_glob(&glob)
198 .chain_err(|| EK::CannotGetReferences(glob))
199 }
200
201 pub fn messages(&self) -> Result<Messages<'r>> {
206 self.terminated_messages()
207 .and_then(|mut messages| {
208 let glob = format!("refs/dit/{}/**", self.ref_part());
211 messages
212 .revwalk
213 .push_glob(glob.as_ref())
214 .chain_err(|| EK::CannotGetReferences(glob))?;
215
216 let glob = format!("refs/remotes/*/dit/{}/**", self.ref_part());
217 messages
218 .revwalk
219 .push_glob(glob.as_ref())
220 .chain_err(|| EK::CannotGetReferences(glob))?;
221
222 Ok(messages)
223 })
224 }
225
226 pub fn messages_from(&self, message: Oid) -> Result<Messages<'r>> {
232 self.terminated_messages()
233 .and_then(|mut messages| {
234 messages
235 .revwalk
236 .push(message)
237 .chain_err(|| EK::CannotConstructRevwalk)?;
238
239 Ok(messages)
240 })
241 }
242
243 pub fn terminated_messages(&self) -> Result<Messages<'r>> {
246 Messages::empty(self.repo)
247 .and_then(|mut messages| {
248 messages.terminate_at_initial(self)?;
250
251 messages.revwalk.simplify_first_parent();
253 messages.revwalk.set_sorting(git2::Sort::TOPOLOGICAL);
254
255 Ok(messages)
256 })
257 }
258
259 pub fn add_message<'a, A, I, J>(&self,
265 author: &git2::Signature,
266 committer: &git2::Signature,
267 message: A,
268 tree: &git2::Tree,
269 parents: I
270 ) -> Result<Commit<'r>>
271 where A: AsRef<str>,
272 I: IntoIterator<Item = &'a Commit<'a>, IntoIter = J>,
273 J: Iterator<Item = &'a Commit<'a>>
274 {
275 let parent_vec : Vec<&Commit> = parents.into_iter().collect();
276
277 self.repo
278 .commit(None, author, committer, message.as_ref(), tree, &parent_vec)
279 .and_then(|id| self.repo.find_commit(id))
280 .chain_err(|| EK::CannotCreateMessage)
281 .and_then(|message| self.add_leaf(message.id()).map(|_| message))
282 }
283
284 pub fn update_head(&self, message: Oid, replace: bool) -> Result<Reference<'r>> {
294 let refname = format!("refs/dit/{}/head", self.ref_part());
295 let reflogmsg = format!("git-dit: set head reference of {} to {}", self, message);
296 self.repo
297 .reference(&refname, message, replace, &reflogmsg)
298 .chain_err(|| EK::CannotSetReference(refname))
299 }
300
301 pub fn add_leaf(&self, message: Oid) -> Result<Reference<'r>> {
306 let refname = format!("refs/dit/{}/leaves/{}", self.ref_part(), message);
307 let reflogmsg = format!("git-dit: new leaf for {}: {}", self, message);
308 self.repo
309 .reference(&refname, message, false, &reflogmsg)
310 .chain_err(|| EK::CannotSetReference(refname))
311 }
312
313 pub fn ref_part(&self) -> String {
320 self.id().to_string()
321 }
322}
323
324impl<'r> fmt::Display for Issue<'r> {
325 fn fmt(&self, f: &mut fmt::Formatter) -> RResult<(), fmt::Error> {
326 write!(f, "{}", self.id())
327 }
328}
329
330impl<'r> PartialEq for Issue<'r> {
331 fn eq(&self, other: &Self) -> bool {
332 self.id() == other.id()
333 }
334}
335
336impl<'r> Eq for Issue<'r> {}
337
338impl<'r> hash::Hash for Issue<'r> {
339 fn hash<H>(&self, state: &mut H)
340 where H: hash::Hasher
341 {
342 self.id().hash(state);
343 }
344}
345
346
347
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use test_utils::TestingRepo;
353
354 use repository::RepositoryExt;
355
356 #[test]
359 fn ref_identification() {
360 {
361 let (id, reftype) = IssueRefType::of_ref("refs/dit/65b56706fdc3501749d008750c61a1f24b888f72/head")
362 .expect("Expected valid issue id and ref type");
363 assert_eq!(id.to_string(), "65b56706fdc3501749d008750c61a1f24b888f72");
364 assert_eq!(reftype, IssueRefType::Head);
365 }
366 {
367 let (id, reftype) = IssueRefType::of_ref("refs/dit/65b56706fdc3501749d008750c61a1f24b888f72/leaves/f6bd121bdc2ba5906e412da19191a2eaf2025755")
368 .expect("Expected valid issue id and ref type");
369 assert_eq!(id.to_string(), "65b56706fdc3501749d008750c61a1f24b888f72");
370 assert_eq!(reftype, IssueRefType::Leaf);
371 }
372
373 assert!(IssueRefType::of_ref("refs/dit/65b56706fdc3501749d008750c61a1f24b888f72/foo/f6bd121bdc2ba5906e412da19191a2eaf2025755").is_none());
374 assert!(IssueRefType::of_ref("refs/dit/65b56706fdc3501749d008750c61a1f24b888f72/head/foo").is_none());
375 assert!(IssueRefType::of_ref("refs/dit/65b56706fdc3501749d008750c61a1f24b888f72/leaves/foo").is_none());
376 assert!(IssueRefType::of_ref("refs/dit/foo/leaves/f6bd121bdc2ba5906e412da19191a2eaf2025755").is_none());
377 assert!(IssueRefType::of_ref("refs/dit/foo/head").is_none());
378 assert!(IssueRefType::of_ref("refs/foo/65b56706fdc3501749d008750c61a1f24b888f72/head").is_none());
379 assert!(IssueRefType::of_ref("refs/foo/65b56706fdc3501749d008750c61a1f24b888f72/leaves/f6bd121bdc2ba5906e412da19191a2eaf2025755").is_none());
380 }
381
382 #[test]
385 fn issue_leaves() {
386 let mut testing_repo = TestingRepo::new("issue_leaves");
387 let repo = testing_repo.repo();
388
389 let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
390 .expect("Could not create signature");
391 let empty_tree = repo
392 .empty_tree()
393 .expect("Could not create empty tree");
394
395 {
396 let issue = repo
398 .create_issue(&sig, &sig, "Test message 1", &empty_tree, vec![])
399 .expect("Could not create issue");
400 let initial_message = issue
401 .initial_message()
402 .expect("Could not retrieve initial message");
403 issue.add_message(&sig, &sig, "Test message 2", &empty_tree, vec![&initial_message])
404 .expect("Could not add message");
405 }
406
407 let issue = repo
408 .create_issue(&sig, &sig, "Test message 3", &empty_tree, vec![])
409 .expect("Could not create issue");
410 let initial_message = issue
411 .initial_message()
412 .expect("Could not retrieve initial message");
413 let message = issue
414 .add_message(&sig, &sig, "Test message 4", &empty_tree, vec![&initial_message])
415 .expect("Could not add message");
416
417 let mut leaves = issue
418 .local_refs(IssueRefType::Leaf)
419 .expect("Could not retrieve issue leaves");
420 let leaf = leaves
421 .next()
422 .expect("Could not find leaf reference")
423 .expect("Could not retrieve leaf reference")
424 .target()
425 .expect("Could not determine the target of the leaf reference");
426 assert_eq!(leaf, message.id());
427 assert!(leaves.next().is_none());
428 }
429
430 #[test]
431 fn local_refs() {
432 let mut testing_repo = TestingRepo::new("local_refs");
433 let repo = testing_repo.repo();
434
435 let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
436 .expect("Could not create signature");
437 let empty_tree = repo
438 .empty_tree()
439 .expect("Could not create empty tree");
440
441 {
442 let issue = repo
444 .create_issue(&sig, &sig, "Test message 1", &empty_tree, vec![])
445 .expect("Could not create issue");
446 let initial_message = issue
447 .initial_message()
448 .expect("Could not retrieve initial message");
449 issue.add_message(&sig, &sig, "Test message 3", &empty_tree, vec![&initial_message])
450 .expect("Could not add message");
451 }
452
453 let issue = repo
454 .create_issue(&sig, &sig, "Test message 2", &empty_tree, vec![])
455 .expect("Could not create issue");
456 let initial_message = issue
457 .initial_message()
458 .expect("Could not retrieve initial message");
459 let message = issue
460 .add_message(&sig, &sig, "Test message 3", &empty_tree, vec![&initial_message])
461 .expect("Could not add message");
462
463 let mut ids = vec![issue.id(), message.id()];
464 ids.sort();
465 let mut ref_ids: Vec<Oid> = issue
466 .local_refs(IssueRefType::Any)
467 .expect("Could not retrieve local refs")
468 .map(|reference| reference.unwrap().target().unwrap())
469 .collect();
470 ref_ids.sort();
471 assert_eq!(ref_ids, ids);
472 }
473
474 #[test]
475 fn message_revwalk() {
476 let mut testing_repo = TestingRepo::new("message_revwalk");
477 let repo = testing_repo.repo();
478
479 let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
480 .expect("Could not create signature");
481 let empty_tree = repo
482 .empty_tree()
483 .expect("Could not create empty tree");
484
485 let issue1 = repo
486 .create_issue(&sig, &sig, "Test message 1", &empty_tree, vec![])
487 .expect("Could not create issue");
488 let initial_message1 = issue1
489 .initial_message()
490 .expect("Could not retrieve initial message");
491
492 let issue2 = repo
493 .create_issue(&sig, &sig, "Test message 2", &empty_tree, vec![&initial_message1])
494 .expect("Could not create issue");
495 let initial_message2 = issue2
496 .initial_message()
497 .expect("Could not retrieve initial message");
498 let message = issue2
499 .add_message(&sig, &sig, "Test message 3", &empty_tree, vec![&initial_message2])
500 .expect("Could not add message");
501 let message_id = message.id();
502
503 let mut iter1 = issue1
504 .messages()
505 .expect("Could not create message revwalk iterator");
506 assert_eq!(iter1.next().unwrap().unwrap().id(), issue1.id());
507 assert!(iter1.next().is_none());
508
509 let mut iter2 = issue2
510 .messages()
511 .expect("Could not create message revwalk iterator");
512 assert_eq!(iter2.next().unwrap().unwrap().id(), message_id);
513 assert_eq!(iter2.next().unwrap().unwrap().id(), issue2.id());
514 assert!(iter2.next().is_none());
515 }
516
517 #[test]
518 fn update_head() {
519 let mut testing_repo = TestingRepo::new("update_head");
520 let repo = testing_repo.repo();
521
522 let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
523 .expect("Could not create signature");
524 let empty_tree = repo
525 .empty_tree()
526 .expect("Could not create empty tree");
527
528 let issue = repo
529 .create_issue(&sig, &sig, "Test message 2", &empty_tree, vec![])
530 .expect("Could not create issue");
531 let initial_message = issue
532 .initial_message()
533 .expect("Could not retrieve initial message");
534 let message = issue
535 .add_message(&sig, &sig, "Test message 3", &empty_tree, vec![&initial_message])
536 .expect("Could not add message");
537
538 assert_eq!(issue.local_head().unwrap().target().unwrap(), issue.id());
539
540 issue
541 .update_head(message.id(), true)
542 .expect("Could not update head reference");
543 assert_eq!(issue.local_head().unwrap().target().unwrap(), message.id());
544 }
545}
546