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
//! What:
//! The text of an isolated merge's commit message and the name Git gives the merged commit in it.
//! Why:
//! The merge commit must read exactly as native Git would write it: the default subject comes from `git
//! fmt-merge-msg`, which `git merge` itself uses, fed the description line `git merge` builds for the commit-ish
//! (`branch 'x'`, `tag 'x'`, `remote-tracking branch 'x'` or `commit 'x'`), and any message is cleaned the way a
//! commit that opens no editor cleans it.
//! A commit-ish whose description cannot be built the same way is not guessed: the caller forwards the command.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const description = await describeMergeSource({ reference, oid });
//! const message = await mergeMessage({ description, log, given });
//! ```
/// Running real Git on a request.
use super::transaction_git::{GitContext, GitRequest, run_git_checked};
/// What:
/// The line `git merge` hands to `git fmt-merge-msg` for one merged commit, or nothing when the name needs a form
/// this reader does not build.
/// Why:
/// `merge_name` in Git's `builtin/merge.c` resolves the name as a reference first: a branch, a tag or a
/// remote-tracking branch is named by its short name and `of .`, and a name that is no reference is a bare commit.
/// The `<name>^` and `<name>~<n>` forms have their own wording, so a name containing `^`, `~`, `@`, `:` or a space
/// and no full reference is left to native Git.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function describeMergeSource({ reference, fullName, oid }): string | undefined;
/// ```
pub fn describe_merge_source(reference: &str, full_name: &str, oid: &str) -> Option<String> {
if let Some(name) = full_name.strip_prefix("refs/heads/") {
return Some(format!("{oid}\t\tbranch '{name}' of .\n"));
}
if let Some(name) = full_name.strip_prefix("refs/tags/") {
return Some(format!("{oid}\t\ttag '{name}' of .\n"));
}
if let Some(name) = full_name.strip_prefix("refs/remotes/") {
return Some(format!("{oid}\t\tremote-tracking branch '{name}' of .\n"));
}
if full_name.is_empty()
&& !reference.is_empty()
&& reference.bytes().all(|byte| { return byte.is_ascii_hexdigit(); })
{
return Some(format!("{oid}\t\tcommit '{reference}'\n"));
}
return None;
}
/// What:
/// The default merge message for a described commit: Git's own `fmt-merge-msg`, with the shortlog choice given.
/// Why:
/// The subject depends on the checked-out branch and on the `merge.log`, `merge.suppressDest` and
/// `merge.branchdesc` settings, which only Git applies.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function defaultMergeMessage({ context, description, log }): Promise<Uint8Array>;
/// ```
pub fn default_merge_message(
context: &GitContext,
description: &str,
log: Option<bool>,
) -> Result<Vec<u8>, String> {
let mut arguments: Vec<&str> = vec!["fmt-merge-msg"];
match log {
Some(true) => arguments.push("--log"),
Some(false) => arguments.push("--no-log"),
None => {}
}
let mut request: GitRequest = GitRequest::new(std::path::Path::new("."), &arguments);
request.input = Some(description.as_bytes().to_vec());
let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
return Ok(output.stdout);
}
/// What:
/// A message cleaned as a commit that opens no editor cleans it: trailing space stripped, blank lines collapsed,
/// one final line feed.
/// Why:
/// `git merge` runs the message through the same cleanup `git commit --cleanup=whitespace` does when it opens no
/// editor; `git stripspace` is that cleanup.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function cleanMessage({ context, message }): Promise<Uint8Array>;
/// ```
pub fn clean_message(context: &GitContext, message: &[u8]) -> Result<Vec<u8>, String> {
let mut request: GitRequest = GitRequest::new(std::path::Path::new("."), &["stripspace"]);
request.input = Some(message.to_vec());
let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
return Ok(output.stdout);
}
/// Description lines and message cleanup.
#[cfg(test)]
#[path = "merge_isolation_message_tests.rs"]
mod tests;