Skip to content

Commit 89f30d2

Browse files
authored
feat: implement github pull request description prompt generator (#22)
1 parent 2000dc6 commit 89f30d2

File tree

6 files changed

+391
-6
lines changed

6 files changed

+391
-6
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ Generate a Git commit message (for staged files):
149149
code2prompt path/to/codebase --diff -t templates/write-git-commit.hbs
150150
```
151151

152+
Generate a Pull Request with branch comparing (for staged files):
153+
154+
```sh
155+
code2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs
156+
```
157+
152158
Add line numbers to source code blocks:
153159

154160
```sh
@@ -188,6 +194,10 @@ Use this template to generate prompts for cleaning up and improving the code qua
188194

189195
Use this template to generate prompts for fixing bugs in the codebase. It will help diagnose issues, provide fix suggestions, and update the code with proposed fixes.
190196

197+
### [`write-github-pull-request.hbs`](templates/write-github-pull-request.hbs)
198+
199+
Use this template to create GitHub pull request description in markdown by comparing the git diff and git log of two branches.
200+
191201
### [`write-github-readme.hbs`](templates/write-github-readme.hbs)
192202

193203
Use this template to generate a high-quality README file for the project, suitable for hosting on GitHub. It will analyze the codebase to understand its purpose and functionality, and generate the README content in Markdown format.

src/git.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use anyhow::{Context, Result};
44
use git2::{DiffOptions, Repository};
5-
use log::info;
5+
use log::{info, error};
66
use std::path::Path;
77

88
/// Generates a git diff for the repository at the provided path
@@ -38,3 +38,112 @@ pub fn get_git_diff(repo_path: &Path) -> Result<String> {
3838
info!("Generated git diff successfully");
3939
Ok(String::from_utf8_lossy(&diff_text).into_owned())
4040
}
41+
42+
/// Generates a git diff between two branches for the repository at the provided path
43+
///
44+
/// # Arguments
45+
///
46+
/// * `repo_path` - A reference to the path of the git repository
47+
/// * `branch1` - The name of the first branch
48+
/// * `branch2` - The name of the second branch
49+
///
50+
/// # Returns
51+
///
52+
/// * `Result<String, git2::Error>` - The generated git diff as a string or an error
53+
pub fn get_git_diff_between_branches(repo_path: &Path, branch1: &str, branch2: &str) -> Result<String> {
54+
info!("Opening repository at path: {:?}", repo_path);
55+
let repo = Repository::open(repo_path).context("Failed to open repository")?;
56+
57+
for branch in [branch1, branch2].iter() {
58+
if !branch_exists(&repo, branch) {
59+
error!("{}",format!("Branch {} doesn't exist!", branch));
60+
std::process::exit(1);
61+
}
62+
}
63+
64+
let branch1_commit = repo.revparse_single(branch1)?.peel_to_commit()?;
65+
let branch2_commit = repo.revparse_single(branch2)?.peel_to_commit()?;
66+
67+
let branch1_tree = branch1_commit.tree()?;
68+
let branch2_tree = branch2_commit.tree()?;
69+
70+
let diff = repo
71+
.diff_tree_to_tree(
72+
Some(&branch1_tree),
73+
Some(&branch2_tree),
74+
Some(DiffOptions::new().ignore_whitespace(true)),
75+
)
76+
.context("Failed to generate diff between branches")?;
77+
78+
let mut diff_text = Vec::new();
79+
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
80+
diff_text.extend_from_slice(line.content());
81+
true
82+
})
83+
.context("Failed to print diff")?;
84+
85+
info!("Generated git diff between branches successfully");
86+
Ok(String::from_utf8_lossy(&diff_text).into_owned())
87+
}
88+
89+
/// Retrieves the git log between two branches for the repository at the provided path
90+
///
91+
/// # Arguments
92+
///
93+
/// * `repo_path` - A reference to the path of the git repository
94+
/// * `branch1` - The name of the first branch (e.g., "master")
95+
/// * `branch2` - The name of the second branch (e.g., "migrate-manifest-v3")
96+
///
97+
/// # Returns
98+
///
99+
/// * `Result<String, git2::Error>` - The git log as a string or an error
100+
pub fn get_git_log(repo_path: &Path, branch1: &str, branch2: &str) -> Result<String> {
101+
info!("Opening repository at path: {:?}", repo_path);
102+
let repo = Repository::open(repo_path).context("Failed to open repository")?;
103+
104+
for branch in [branch1, branch2].iter() {
105+
if !branch_exists(&repo, branch) {
106+
error!("{}",format!("Branch {} doesn't exist!", branch));
107+
std::process::exit(1);
108+
}
109+
}
110+
111+
let branch1_commit = repo.revparse_single(branch1)?.peel_to_commit()?;
112+
let branch2_commit = repo.revparse_single(branch2)?.peel_to_commit()?;
113+
114+
let mut revwalk = repo.revwalk().context("Failed to create revwalk")?;
115+
revwalk.push(branch2_commit.id()).context("Failed to push branch2 commit to revwalk")?;
116+
revwalk.hide(branch1_commit.id()).context("Failed to hide branch1 commit from revwalk")?;
117+
revwalk.set_sorting(git2::Sort::REVERSE)?;
118+
119+
let mut log_text = String::new();
120+
for oid in revwalk {
121+
let oid = oid.context("Failed to get OID from revwalk")?;
122+
let commit = repo.find_commit(oid).context("Failed to find commit")?;
123+
log_text.push_str(&format!(
124+
"{} - {}\n",
125+
&commit.id().to_string()[..7],
126+
commit.summary().unwrap_or("No commit message")
127+
));
128+
}
129+
130+
info!("Retrieved git log successfully");
131+
Ok(log_text)
132+
}
133+
134+
/// Checks if a local branch exists in the given repository
135+
///
136+
/// # Arguments
137+
///
138+
/// * `repo` - A reference to the `Repository` where the branch should be checked
139+
/// * `branch_name` - A string slice that holds the name of the branch to check
140+
///
141+
/// # Returns
142+
///
143+
/// * `bool` - `true` if the branch exists, `false` otherwise
144+
fn branch_exists(repo: &Repository, branch_name: &str) -> bool {
145+
match repo.find_branch(branch_name, git2::BranchType::Local) {
146+
Ok(_) => true,
147+
Err(_) => false,
148+
}
149+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub mod template;
55
pub mod token;
66

77
pub use filter::should_include_file;
8-
pub use git::get_git_diff;
8+
pub use git::{get_git_diff, get_git_diff_between_branches, get_git_log};
99
pub use path::{label, traverse_directory};
1010
pub use template::{
1111
copy_to_clipboard, handle_undefined_variables, handlebars_setup, render_template, write_to_file,

src/main.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
use anyhow::{Context, Result};
77
use clap::Parser;
88
use code2prompt::{
9-
copy_to_clipboard, get_model_info, get_tokenizer, get_git_diff, handle_undefined_variables, handlebars_setup,
10-
label, render_template, traverse_directory, write_to_file,
9+
copy_to_clipboard, get_model_info, get_tokenizer, get_git_diff, get_git_diff_between_branches, get_git_log,
10+
handle_undefined_variables, handlebars_setup, label, render_template, traverse_directory, write_to_file,
1111
};
1212
use colored::*;
1313
use indicatif::{ProgressBar, ProgressStyle};
14-
use log::debug;
14+
use log::{debug, error};
1515
use serde_json::json;
1616
use std::path::PathBuf;
1717

@@ -61,6 +61,14 @@ struct Cli {
6161
#[clap(short, long)]
6262
diff: bool,
6363

64+
/// Generate git diff between two branches
65+
#[clap(long, value_name = "BRANCHES")]
66+
git_diff_branch: Option<String>,
67+
68+
/// Retrieve git log between two branches
69+
#[clap(long, value_name = "BRANCHES")]
70+
git_log_branch: Option<String>,
71+
6472
/// Add line numbers to the source code
6573
#[clap(short, long)]
6674
line_number: bool,
@@ -136,6 +144,30 @@ fn main() -> Result<()> {
136144
String::new()
137145
};
138146

147+
// git diff two get_git_diff_between_branches
148+
let mut git_diff_branch: String = String::new();
149+
if let Some(branches) = &args.git_diff_branch {
150+
spinner.set_message("Generating git diff between two branches...");
151+
let branches = parse_patterns(&Some(branches.to_string()));
152+
if branches.len() != 2 {
153+
error!("Please provide exactly two branches separated by a comma.");
154+
std::process::exit(1);
155+
}
156+
git_diff_branch = get_git_diff_between_branches(&args.path, &branches[0], &branches[1]).unwrap_or_default()
157+
}
158+
159+
// git diff two get_git_diff_between_branches
160+
let mut git_log_branch: String = String::new();
161+
if let Some(branches) = &args.git_log_branch {
162+
spinner.set_message("Generating git log between two branches...");
163+
let branches = parse_patterns(&Some(branches.to_string()));
164+
if branches.len() != 2 {
165+
error!("Please provide exactly two branches separated by a comma.");
166+
std::process::exit(1);
167+
}
168+
git_log_branch = get_git_log(&args.path, &branches[0], &branches[1]).unwrap_or_default()
169+
}
170+
139171
spinner.finish_with_message("Done!".green().to_string());
140172

141173
// Prepare JSON Data
@@ -144,6 +176,8 @@ fn main() -> Result<()> {
144176
"source_tree": tree,
145177
"files": files,
146178
"git_diff": git_diff,
179+
"git_diff_branch": git_diff_branch,
180+
"git_log_branch": git_log_branch
147181
});
148182

149183
debug!(
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
Project Path: {{ absolute_code_path }}
2+
3+
I want you to generate a high-quality well-crafted Github pull request description for this project.
4+
I will provide you with the source tree, git diff, git log, and pull request template.
5+
6+
Source Tree:
7+
```
8+
{{ source_tree }}
9+
```
10+
11+
{{#if git_diff_branch}}
12+
Git diff:
13+
```
14+
{{git_diff_branch}}
15+
```
16+
{{/if}}
17+
18+
19+
{{#if git_log_branch}}
20+
Git log:
21+
```
22+
{{git_log_branch}}
23+
```
24+
{{/if}}
25+
26+
27+
The Pull Request description should include the following template and adhere best practice:
28+
```
29+
Title: provide with concise and informative title.
30+
31+
# What is this?
32+
- Explain the motivation why this is needed and the expected outcome of implementing this.
33+
- Write it in a humanized manner.
34+
35+
# Changes
36+
- Provide list of key changes with good structure.
37+
- Mention the class name, function name, and file name.
38+
- Explain the code changes.
39+
For example:
40+
# Changes
41+
## Added Features:
42+
1. **New Functions in `file_name.`**:
43+
- `function_name.`: code description.
44+
## Code Changes:
45+
1. **In `file_name.`**:
46+
## Documentation Updates:
47+
1. **In `file_name.`**:
48+
49+
# Demo
50+
- N/A
51+
52+
# Context
53+
- N/A
54+
```
55+
56+
Please, analyze the git diff and git log to understand the changes. Do not output git log and git diff to the content.
57+
Use your analysis of the code to generate accurate and helpful content, but also explain things clearly for users who may not be familiar with the implementation details.
58+
Write the content in Markdown format and follow the provided pull request template.

0 commit comments

Comments
 (0)