1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
90
91
92
93
94
95
96
97
98
use git2::{Error, Oid, RebaseOptions, Repository, Signature, Time};
use crate::utils::{count_commits_from, find_closest_git_repo};
use crate::GitTimeTravel;
use rand::Rng;
use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};
use rand::thread_rng;
impl GitTimeTravel {
pub fn run(&self) -> Result<(), Error> {
let repo = find_closest_git_repo()?;
let oid = repo.revparse_single(&self.commit)?.id();
let commits = count_commits_from(oid, &repo)?;
let start_time = self.start_time()?;
let end_time = self.end_time(commits)?;
let mut dates = vec![];
let mut rng = thread_rng();
for _ in 0..commits {
let random_time = rng.gen_range(start_time.timestamp()..end_time.timestamp());
dates.push(Time::new(random_time, 0));
}
rebase_to_branch(&self.branch_name(), oid, &repo, &dates)?;
Ok(())
}
fn start_time(&self) -> Result<NaiveDateTime, Error> {
let date = match NaiveDate::parse_from_str(&self.start_date, "%Y-%m-%d") {
Ok(o) => { o }
Err(_) => { Err(Error::from_str("date parse failed"))? }
};
Ok(date.and_time(NaiveTime::MIN))
}
fn end_time(&self, days: usize) -> Result<NaiveDateTime, Error> {
match &self.end_date {
Some(s) => {
let date = match NaiveDate::parse_from_str(&s, "%Y-%m-%d") {
Ok(o) => { o }
Err(_) => { Err(Error::from_str("date parse failed"))? }
};
Ok(date.and_time(NaiveTime::MIN))
}
None => {
let start_time = self.start_time()?;
Ok(start_time + Duration::days(days as i64))
}
}
}
fn branch_name(&self) -> String {
match &self.branch {
Some(s) => {
s.to_string()
}
None => {
"time-travel".to_string()
}
}
}
}
fn rebase_to_branch(name: &str, id: Oid, repo: &Repository, dates: &[Time]) -> Result<(), Error> {
let annotated = repo.find_annotated_commit(id)?;
let mut rebase_options = RebaseOptions::new();
rebase_options.inmemory(true);
let mut last = id;
let mut rebase = repo.rebase(None, Some(&annotated), None, Some(&mut rebase_options))?;
let mut dates = dates.iter();
while let Some(operation) = rebase.next() {
let commit = repo.find_commit(operation?.id())?;
let mut author = commit.author();
let mut committer = commit.committer();
match dates.next() {
Some(s) => {
author = new_sign(author, *s)?;
committer = new_sign(committer, *s)?;
}
None => {
panic!("Not enough dates provided")
}
}
last = rebase.commit(Some(&author), &committer, commit.message())?;
}
rebase.finish(None)?;
let target = repo.find_commit(last)?;
repo.branch(name, &target, true)?;
Ok(())
}
fn new_sign(old: Signature, date: Time) -> Result<Signature, Error> {
let name = String::from_utf8_lossy(old.name_bytes());
let email = String::from_utf8_lossy(old.email_bytes());
Signature::new(name.as_ref(), email.as_ref(), &date)
}