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
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
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Backup {
pub branches: Vec<Branch>,
#[serde(default)]
#[serde(skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub metadata: std::collections::BTreeMap<String, serde_json::Value>,
}
impl Backup {
pub fn load(path: &std::path::Path) -> Result<Self, std::io::Error> {
let file = std::fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
let b = serde_json::from_reader(reader)?;
Ok(b)
}
pub fn save(&self, path: &std::path::Path) -> Result<(), std::io::Error> {
let s = serde_json::to_string_pretty(self)?;
std::fs::write(path, &s)?;
Ok(())
}
pub fn from_repo(repo: &dyn crate::git::Repo) -> Result<Self, git2::Error> {
let mut branches: Vec<_> = repo
.local_branches()
.map(|b| {
let commit = repo.find_commit(b.id).unwrap();
Branch {
name: b.name,
id: b.id,
metadata: maplit::btreemap! {
"summary".to_owned() => serde_json::Value::String(
String::from_utf8_lossy(commit.summary.as_slice()).into_owned()
),
},
}
})
.collect();
branches.sort_unstable();
let metadata = Default::default();
Ok(Self { branches, metadata })
}
pub fn apply(&self, repo: &mut dyn crate::git::Repo) -> Result<(), git2::Error> {
let head_branch = repo.head_branch();
let head_branch_name = head_branch.as_ref().map(|b| b.name.as_str());
for branch in self.branches.iter() {
let existing = repo.find_local_branch(&branch.name);
if existing.map(|b| b.id) == Some(branch.id) {
log::trace!("No change for {}", branch.name);
} else {
if head_branch_name == Some(branch.name.as_str()) {
log::debug!("Restoring {} (HEAD)", branch.name);
repo.detach()?;
repo.branch(&branch.name, branch.id)?;
repo.switch(&branch.name)?;
} else {
log::debug!("Restoring {}", branch.name);
repo.branch(&branch.name, branch.id)?;
}
}
}
Ok(())
}
pub fn insert_message(&mut self, message: &str) {
self.metadata.insert(
"message".to_owned(),
serde_json::Value::String(message.to_owned()),
);
}
pub fn insert_parent(
&mut self,
repo: &dyn crate::git::Repo,
branches: &crate::git::Branches,
protected_branches: &crate::git::Branches,
) {
for branch in self.branches.iter_mut() {
if let Some(parent) = crate::git::find_base(repo, branches, branch.id)
.or_else(|| crate::git::find_protected_base(repo, protected_branches, branch.id))
{
branch.metadata.insert(
"parent".to_owned(),
serde_json::Value::String(parent.name.clone()),
);
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Branch {
pub name: String,
#[serde(serialize_with = "serialize_oid")]
#[serde(deserialize_with = "deserialize_oid")]
pub id: git2::Oid,
#[serde(default)]
#[serde(skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub metadata: std::collections::BTreeMap<String, serde_json::Value>,
}
fn serialize_oid<S>(id: &git2::Oid, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let id = id.to_string();
serializer.serialize_str(&id)
}
fn deserialize_oid<'de, D>(deserializer: D) -> Result<git2::Oid, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let s = String::deserialize(deserializer)?;
git2::Oid::from_str(&s).map_err(serde::de::Error::custom)
}
impl PartialOrd for Branch {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some((&self.name, self.id).cmp(&(&other.name, other.id)))
}
}
impl Ord for Branch {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(&self.name, self.id).cmp(&(&other.name, other.id))
}
}