Skip to content

Commit 6097135

Browse files
[REVIEW] feat: add patchset apply action
Introduce an action to the "Patchset Details and Actions" screen to apply a patchset to a target kernel tree, i.e., apply the patches in order using `git am` using a Linux kernel repo and a branch as base. This action should be a core feature of `patch-hub` and it will be much more refined and expanded. However, some effort was put into handling virtually all setup errors (no configs set, invalid branch, rebase in progress, etc.) and into clean up after the fact (abort the `git am` in case if fails, and checkout back to the original branch either way). Currently, there is only a binding of the `a` key to the action, but no UI drawn. The function binded with the key always returns a string with information about the status of the action, which can then easilly be displayed to the user in a `PopUp` type. Signed-off-by: David Tadokoro <[email protected]>
1 parent 6755d6d commit 6097135

File tree

3 files changed

+355
-58
lines changed

3 files changed

+355
-58
lines changed

foo.patch

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
diff --git a/src/app/screens/details_actions.rs b/src/app/screens/details_actions.rs
2+
index 77e85cf..3eae052 100644
3+
--- a/src/app/screens/details_actions.rs
4+
+++ b/src/app/screens/details_actions.rs
5+
@@ -1,4 +1,4 @@
6+
-use crate::app::{config::Config, logging::Logger};
7+
+use crate::app::config::Config;
8+
9+
use super::CurrentScreen;
10+
use ::patch_hub::lore::{lore_api_client::BlockingLoreAPIClient, lore_session, patch::Patch};
11+
@@ -206,80 +206,151 @@ impl DetailsActions {
12+
Ok(())
13+
}
14+
15+
- /// Apply the patchset to the current selected kernel tree
16+
- pub fn apply_patchset(&self, config: &Config) {
17+
- let tree = config.current_tree().as_ref().unwrap();
18+
- let tree_path = config.kernel_tree_path(tree).unwrap();
19+
- let am_options = config.git_am_options();
20+
- let branch_prefix = config.git_am_branch_prefix();
21+
- // TODO: Select a kernel tree
22+
-
23+
- // Change the current working directory to the tree_path
24+
- // Save the old working directory
25+
- let oldwd = std::env::current_dir().unwrap();
26+
-
27+
- std::env::set_current_dir(tree_path).unwrap();
28+
- // TODO: Select a branch
29+
- // 3. Create a new branch
30+
- let branch_name = format!(
31+
+ /// Try to apply the patchset to a target kernel tree and returns a `String`
32+
+ /// informing if the apply succeeded or failed and why.
33+
+ pub fn apply_patchset(&self, config: &Config) -> String {
34+
+ // 1. Check if target kernel tree is set
35+
+ let kernel_tree_id = if let Some(target) = config.target_kernel_tree() {
36+
+ target
37+
+ } else {
38+
+ return "[error] target kernel tree unset".to_string();
39+
+ };
40+
+
41+
+ // 2. Check if target kernel tree exists
42+
+ let kernel_tree = if let Some(tree) = config.get_kernel_tree(kernel_tree_id) {
43+
+ tree
44+
+ } else {
45+
+ return format!("[error] invalid target kernel tree '{}'", kernel_tree_id);
46+
+ };
47+
+
48+
+ // 3. Check if path to kernel tree is valid
49+
+ let kernel_tree_path = Path::new(kernel_tree.path());
50+
+ if !kernel_tree_path.is_dir() {
51+
+ return format!("[error] {} isn't a directory", kernel_tree.path());
52+
+ } else if !kernel_tree_path.join(".git").is_dir() {
53+
+ return format!("[error] {} isn't a git repository", kernel_tree.path());
54+
+ }
55+
+
56+
+ // 4. Check if there are any `git rebase`, `git merge`, `git bisect`, or
57+
+ // `git am` already happening
58+
+ if kernel_tree_path.join(".git/rebase-merge").is_dir() {
59+
+ return "[error] rebase in progress. \nrun `git rebase --abort` before continuing"
60+
+ .to_string();
61+
+ } else if kernel_tree_path.join(".git/MERGE_HEAD").is_file() {
62+
+ return "[error] merge in progress. \nrun `git rebase --abort` before continuing"
63+
+ .to_string();
64+
+ } else if kernel_tree_path.join(".git/BISECT_LOG").is_file() {
65+
+ return "[error] bisect in progress. \nrun `git bisect reset` before continuing"
66+
+ .to_string();
67+
+ } else if kernel_tree_path.join(".git/rebase-apply").is_dir() {
68+
+ return "[error] `git am` already in progress. \nrun `git am --abort` before continuing".to_string();
69+
+ }
70+
+
71+
+ // 5. Check if there are any staged or unstaged changes
72+
+ let git_status_out = Command::new("git")
73+
+ .arg("-C")
74+
+ .arg(kernel_tree.path())
75+
+ .arg("status")
76+
+ .arg("--porcelain")
77+
+ .output()
78+
+ .unwrap();
79+
+ let git_status_out = String::from_utf8_lossy(&git_status_out.stdout);
80+
+ if !git_status_out.is_empty() {
81+
+ return format!(
82+
+ "[error] there are staged and/or unstaged changes\n{}",
83+
+ git_status_out
84+
+ );
85+
+ }
86+
+
87+
+ // 6. Check if base branch is valid
88+
+ let git_show_ref_out = Command::new("git")
89+
+ .arg("-C")
90+
+ .arg(kernel_tree.path())
91+
+ .arg("show-ref")
92+
+ .arg("--verify")
93+
+ .arg("--quiet")
94+
+ .arg(format!("refs/heads/{}", kernel_tree.branch()))
95+
+ .output()
96+
+ .unwrap();
97+
+ if !git_show_ref_out.status.success() {
98+
+ return format!(
99+
+ "[error] invalid branch '{}' for '{}'",
100+
+ kernel_tree.branch(),
101+
+ kernel_tree.path()
102+
+ );
103+
+ }
104+
+
105+
+ // 7. Save original branch, switch to base branch, and checkout to target
106+
+ // branch
107+
+ let original_branch = Command::new("git")
108+
+ .arg("-C")
109+
+ .arg(kernel_tree.path())
110+
+ .arg("rev-parse")
111+
+ .arg("--abbrev-ref")
112+
+ .arg("HEAD")
113+
+ .output()
114+
+ .unwrap();
115+
+ let mut original_branch = String::from_utf8_lossy(&original_branch.stdout).to_string();
116+
+ original_branch.pop();
117+
+ let _ = Command::new("git")
118+
+ .arg("-C")
119+
+ .arg(kernel_tree.path())
120+
+ .arg("switch")
121+
+ .arg(kernel_tree.branch())
122+
+ .output()
123+
+ .unwrap();
124+
+ let target_branch_name = format!(
125+
"{}{}",
126+
- branch_prefix,
127+
+ config.git_am_branch_prefix(),
128+
chrono::Utc::now().format("%Y-%m-%d-%H-%M-%S")
129+
);
130+
let _ = Command::new("git")
131+
+ .arg("-C")
132+
+ .arg(kernel_tree.path())
133+
.arg("checkout")
134+
.arg("-b")
135+
- .arg(&branch_name)
136+
+ .arg(&target_branch_name)
137+
.output()
138+
.unwrap();
139+
140+
- // 3. Apply the patchset
141+
- let mut cmd = Command::new("git");
142+
-
143+
- cmd.arg("am").arg(&self.path);
144+
-
145+
- am_options.split_whitespace().for_each(|opt| {
146+
- cmd.arg(opt);
147+
+ // 8. Apply the patchset
148+
+ let mut git_am_out = Command::new("git");
149+
+ git_am_out
150+
+ .arg("-C")
151+
+ .arg(kernel_tree.path())
152+
+ .arg("am")
153+
+ .arg(&self.patchset_path);
154+
+ config.git_am_options().split_whitespace().for_each(|opt| {
155+
+ git_am_out.arg(opt);
156+
});
157+
-
158+
- let out = cmd.output().unwrap();
159+
-
160+
- if !out.status.success() {
161+
- Logger::error(format!(
162+
- "Failed to apply the patchset `{}`",
163+
- self.representative_patch.title()
164+
- ));
165+
- Logger::error(String::from_utf8_lossy(&out.stderr));
166+
+ let git_am_out = git_am_out.output().unwrap();
167+
+ if !git_am_out.status.success() {
168+
let _ = Command::new("git")
169+
+ .arg("-C")
170+
+ .arg(kernel_tree.path())
171+
.arg("am")
172+
.arg("--abort")
173+
.output()
174+
.unwrap();
175+
- } else {
176+
- Logger::info(format!(
177+
- "Patchset `{}` applied successfully to `{}` tree at branch `{}`",
178+
- self.representative_patch.title(),
179+
- tree,
180+
- branch_name
181+
- ));
182+
}
183+
184+
- // 4. git checkout -
185+
+ // 9. Return back to original branch
186+
let _ = Command::new("git")
187+
- .arg("checkout")
188+
- .arg("-")
189+
+ .arg("-C")
190+
+ .arg(kernel_tree.path())
191+
+ .arg("switch")
192+
+ .arg(&original_branch)
193+
.output()
194+
.unwrap();
195+
196+
- if !out.status.success() {
197+
- let _ = Command::new("git")
198+
- .arg("branch")
199+
- .arg("-D")
200+
- .arg(&branch_name)
201+
- .output()
202+
- .unwrap();
203+
+ if git_am_out.status.success() {
204+
+ format!("[success] patchset '{}' applied successfully to '{}' tree in branch '{}' (based on '{}' branch)", self.representative_patch.title(), kernel_tree.path(), &target_branch_name, kernel_tree.branch())
205+
+ } else {
206+
+ format!(
207+
+ "[error] `git am` failed\n{}{}",
208+
+ &original_branch,
209+
+ String::from_utf8_lossy(&git_am_out.stderr)
210+
+ )
211+
}
212+
- // 5. CD back
213+
- std::env::set_current_dir(&oldwd).unwrap();
214+
}
215+
}

0 commit comments

Comments
 (0)