summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Dilly <dilly.stephan@gmail.com>2021-05-12 15:34:36 +0200
committerStephan Dilly <dilly.stephan@gmail.com>2021-05-12 15:34:36 +0200
commit378b8655ab02d970feaa36989d20e0c6cd15f5f8 (patch)
treecf29969c6372c339dd3adc94b04098779eb9df49
parentb6ea2b77d103b73ed585c727bdd5ffb6d7910707 (diff)
parent017701ffac5f35447ce9d25faae2ad5456d04760 (diff)
Merge branch 'master'fix-176-win-maxpath
Conflicts: Cargo.lock asyncgit/Cargo.toml
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--CHANGELOG.md4
-rw-r--r--Cargo.lock34
-rw-r--r--README.md11
-rw-r--r--asyncgit/Cargo.toml5
-rw-r--r--asyncgit/src/sync/branch/merge_commit.rs110
-rw-r--r--asyncgit/src/sync/branch/merge_ff.rs6
-rw-r--r--asyncgit/src/sync/merge.rs141
-rw-r--r--asyncgit/src/sync/mod.rs4
-rw-r--r--src/app.rs7
-rw-r--r--src/components/branchlist.rs34
-rw-r--r--src/components/commit.rs88
-rw-r--r--src/components/create_branch.rs2
-rw-r--r--src/components/reset.rs14
-rw-r--r--src/components/utils/filetree.rs1
-rw-r--r--src/input.rs68
-rw-r--r--src/keys.rs10
-rw-r--r--src/main.rs28
-rw-r--r--src/queue.rs3
-rw-r--r--src/strings.rs43
-rw-r--r--src/tabs/status.rs145
-rw-r--r--vim_style_key_config.ron3
22 files changed, 547 insertions, 216 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a4744f50..df739140 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
- name: MacOS Workaround
if: matrix.os == 'macos-latest'
- run: cargo clean --locked -p serde_derive -p thiserror
+ run: cargo clean -p serde_derive -p thiserror
- name: Install Rust
uses: actions-rs/toolchain@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c648c3f4..40ac8e31 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,10 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
![warning](assets/commit-msg-length-limit.gif)
## Added
+- merging arbitrary branch, commit merges ([#485](https://github.com/extrawurst/gitui/issues/485))
- warning if commit subject line gets too long ([#478](https://github.com/extrawurst/gitui/issues/478))
## Changed
-- smarter log timestamps ([#682](https://github.com/extrawurst/gitui/issues/682))
+- smarter log timestamps ([#682](https://github.com/extrawurst/gitui/issues/682))
+- create-branch popup aligned with rename-branch [[@bruceCoelho](https://github.com/bruceCoelho)] ([#679](https://github.com/extrawurst/gitui/issues/679))
## [0.15.0] - 2020-04-27
diff --git a/Cargo.lock b/Cargo.lock
index 8983c508..9b9f2f14 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "addr2line"
-version = "0.14.1"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
+checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a"
dependencies = [
"gimli",
]
@@ -78,9 +78,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
-version = "0.3.58"
+version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82"
+checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744"
dependencies = [
"addr2line",
"cc",
@@ -306,14 +306,15 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.23.0"
+version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
[[package]]
name = "git2"
-version = "0.13.18"
-source = "git+https://github.com/rust-lang/git2-rs.git?rev=1b1499a#1b1499a78dc04c042aa7789f81f0a3308b66261b"
+version = "0.13.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17929de7239dea9f68aa14f94b2ab4974e7b24c1314275ffcc12a7758172fa18"
dependencies = [
"bitflags",
"libc",
@@ -466,8 +467,9 @@ checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "libgit2-sys"
-version = "0.12.19+1.1.0"
-source = "git+https://github.com/rust-lang/git2-rs.git?rev=1b1499a#1b1499a78dc04c042aa7789f81f0a3308b66261b"
+version = "0.12.20+1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e2f09917e00b9ad194ae72072bb5ada2cca16d8171a43e91ddba2afbb02664b"
dependencies = [
"cc",
"libc",
@@ -652,9 +654,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.23.0"
+version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
+checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
[[package]]
name = "once_cell"
@@ -664,9 +666,9 @@ checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "openssl-probe"
-version = "0.1.2"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-src"
@@ -1168,9 +1170,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
-version = "2.2.1"
+version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
diff --git a/README.md b/README.md
index 978c4482..5d71093f 100644
--- a/README.md
+++ b/README.md
@@ -55,11 +55,11 @@
For a [RustBerlin meetup presentation](https://youtu.be/rpilJV-eIVw?t=5334) ([slides](https://github.com/extrawurst/gitui-presentation)) I compared `lazygit`,`tig` and `gitui` by parsing the entire Linux git repository (which contains over 900k commits):
-| | Time | Memory (GB) | Binary (MB) | Freezes | Crashes |
-| --------- | ----------- | ----------- | ----------- | --------- | --------- |
-| `gitui` | **24 s** ✅ | **0.17** ✅ | 1.4 | **No** ✅ | **No** ✅ |
-| `lazygit` | 57 s | 2.6 | 16 | Yes | Sometimes |
-| `tig` | 4 m 20 s | 1.3 | **0.6** ✅ | Sometimes | **No** ✅ |
+| | Time | Memory (GB) | Binary (MB) | Freezes | Crashes |
+| --------- | ---------- | ----------- | ----------- | --------- | --------- |
+| `gitui` | **24 s** ✅ | **0.17** ✅ | 1.4 | **No** ✅ | **No** ✅ |
+| `lazygit` | 57 s | 2.6 | 16 | Yes | Sometimes |
+| `tig` | 4 m 20 s | 1.3 | **0.6** ✅ | Sometimes | **No** ✅ |
## 3. <a name="motivation"></a> Motivation <small><sup>[Top ▲](#table-of-contents)</sup></small>
@@ -71,7 +71,6 @@ Over the last 2 years my go-to GUI tool for this was [fork](https://git-fork.com
These are the high level goals before calling out `1.0`:
-* merging with conflicts ([#485](https://github.com/extrawurst/gitui/issues/485))
* log search (commit, author, sha) ([#449](https://github.com/extrawurst/gitui/issues/449),[#429](https://github.com/extrawurst/gitui/issues/429))
* file history log ([#381](https://github.com/extrawurst/gitui/issues/381))
* more tag support ([#483](https://github.com/extrawurst/gitui/issues/483))
diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml
index 894c8481..b1872a65 100644
--- a/asyncgit/Cargo.toml
+++ b/asyncgit/Cargo.toml
@@ -13,8 +13,9 @@ keywords = ["git"]
[dependencies]
scopetime = { path = "../scopetime", version = "0.1" }
-# git2 = { version = "0.13", features = ["vendored-openssl"] }
-git2 = { git="https://github.com/rust-lang/git2-rs.git", rev="1b1499a", features = ["vendored-openssl"] }
+git2 = { version = "0.13", features = ["vendored-openssl"] }
+# git2 = { path = "../../github/git2-rs", features = ["vendored-openssl"]}
+# git2 = { git="https://github.com/extrawurst/git2-rs.git", rev="513a8c9", features = ["vendored-openssl"]}
rayon-core = "1.9"
crossbeam-channel = "0.5"
log = "0.4"
diff --git a/asyncgit/src/sync/branch/merge_commit.rs b/asyncgit/src/sync/branch/merge_commit.rs
index 40e436c6..2084bd12 100644
--- a/asyncgit/src/sync/branch/merge_commit.rs
+++ b/asyncgit/src/sync/branch/merge_commit.rs
@@ -3,16 +3,18 @@
use super::BranchType;
use crate::{
error::{Error, Result},
- sync::{utils, CommitId},
+ sync::{merge_msg, utils, CommitId},
};
-use git2::MergeOptions;
+use git2::Commit;
use scopetime::scope_time;
-/// merge upstream using a merge commit without conflicts. fails if not possible without conflicts
+/// merge upstream using a merge commit if we did not create conflicts.
+/// if we did not create conflicts we create a merge commit and return the commit id.
+/// Otherwise we return `None`
pub fn merge_upstream_commit(
repo_path: &str,
branch_name: &str,
-) -> Result<CommitId> {
+) -> Result<Option<CommitId>> {
scope_time!("merge_upstream_commit");
let repo = utils::repo(repo_path)?;
@@ -22,10 +24,10 @@ pub fn merge_upstream_commit(
let upstream_commit = upstream.get().peel_to_commit()?;
- let annotated_upstream =
- repo.find_annotated_commit(upstream_commit.id())?;
+ let annotated_upstream = repo
+ .reference_to_annotated_commit(&upstream.into_reference())?;
- let (analysis, _) =
+ let (analysis, pref) =
repo.merge_analysis(&[&annotated_upstream])?;
if !analysis.is_normal() {
@@ -34,61 +36,59 @@ pub fn merge_upstream_commit(
));
}
- //TODO: support merge on unborn
+ if analysis.is_fast_forward() && pref.is_fastforward_only() {
+ return Err(Error::Generic(
+ "ff merge would be possible".into(),
+ ));
+ }
+
+ //TODO: support merge on unborn?
if analysis.is_unborn() {
return Err(Error::Generic("head is unborn".into()));
}
- let mut opt = MergeOptions::default();
- opt.fail_on_conflict(true);
+ repo.merge(&[&annotated_upstream], None, None)?;
+
+ if !repo.index()?.has_conflicts() {
+ let msg = merge_msg(repo_path)?;
- repo.merge(&[&annotated_upstream], Some(&mut opt), None)?;
+ let commit_id =
+ commit_merge_with_head(&repo, &[upstream_commit], &msg)?;
- if repo.index()?.has_conflicts() {
- return Err(Error::Generic("creates conflicts".into()));
+ return Ok(Some(commit_id));
}
+ Ok(None)
+}
+
+pub(crate) fn commit_merge_with_head(
+ repo: &git2::Repository,
+ commits: &[Commit],
+ msg: &str,
+) -> Result<CommitId> {
let signature =
- crate::sync::commit::signature_allow_undefined_name(&repo)?;
+ crate::sync::commit::signature_allow_undefined_name(repo)?;
let mut index = repo.index()?;
let tree_id = index.write_tree()?;
let tree = repo.find_tree(tree_id)?;
-
let head_commit = repo.find_commit(
- crate::sync::utils::get_head_repo(&repo)?.into(),
+ crate::sync::utils::get_head_repo(repo)?.into(),
)?;
- let parents = vec![&head_commit, &upstream_commit];
-
- //find remote url for this branch
- let remote_url = {
- let branch_refname =
- branch.get().name().ok_or_else(|| {
- Error::Generic(String::from(
- "branch refname not found",
- ))
- })?;
- let buf = repo.branch_upstream_remote(branch_refname)?;
- let remote =
- repo.find_remote(buf.as_str().ok_or_else(|| {
- Error::Generic(String::from("remote name not found"))
- })?)?;
- remote.url().unwrap_or_default().to_string()
- };
+
+ let mut parents = vec![&head_commit];
+ parents.extend(commits);
let commit_id = repo
.commit(
Some("HEAD"),
&signature,
&signature,
- format!("Merge '{}' of {}", branch_name, remote_url)
- .as_str(),
+ msg,
&tree,
parents.as_slice(),
)?
.into();
-
repo.cleanup_state()?;
-
Ok(commit_id)
}
@@ -160,7 +160,9 @@ mod test {
);
let merge_commit =
- merge_upstream_commit(clone2_dir, "master").unwrap();
+ merge_upstream_commit(clone2_dir, "master")
+ .unwrap()
+ .unwrap();
let state = crate::sync::repo_state(clone2_dir).unwrap();
assert_eq!(state, RepoState::Clean);
@@ -179,15 +181,12 @@ mod test {
.unwrap();
assert_eq!(
details.message.unwrap().combine(),
- format!(
- "Merge 'master' of {}",
- r1_dir.path().to_str().unwrap()
- )
+ String::from("Merge remote-tracking branch 'refs/remotes/origin/master'")
);
}
#[test]
- fn test_merge_normal_conflict() {
+ fn test_merge_normal_non_ff() {
let (r1_dir, _repo) = repo_init_bare().unwrap();
let (clone1_dir, clone1) =
@@ -198,7 +197,12 @@ mod test {
// clone1
- write_commit_file(&clone1, "test.bin", "test", "commit1");
+ write_commit_file(
+ &clone1,
+ "test.bin",
+ "test\nfooo",
+ "commit1",
+ );
debug_cmd_print(
clone2_dir.path().to_str().unwrap(),
@@ -217,7 +221,12 @@ mod test {
// clone2
- write_commit_file(&clone2, "test.bin", "foobar", "commit2");
+ write_commit_file(
+ &clone2,
+ "test.bin",
+ "foobar\ntest",
+ "commit2",
+ );
let bytes = fetch(
clone2_dir.path().to_str().unwrap(),
@@ -231,18 +240,19 @@ mod test {
let res = merge_upstream_commit(
clone2_dir.path().to_str().unwrap(),
"master",
- );
+ )
+ .unwrap();
- //this should have failed cause it would create a conflict
- assert!(res.is_err());
+ //this should not have commited cause we left conflicts behind
+ assert_eq!(res, None);
let state = crate::sync::repo_state(
clone2_dir.path().to_str().unwrap(),
)
.unwrap();
- //make sure we left the repo not in some merging state
- assert_eq!(state, RepoState::Clean);
+ //validate the repo is in a merge state now
+ assert_eq!(state, RepoState::Merge);
//check that we still only have the first commit
let commits = get_commit_ids(&clone1, 10);
diff --git a/asyncgit/src/sync/branch/merge_ff.rs b/asyncgit/src/sync/branch/merge_ff.rs
index f5265a70..fb45fe67 100644
--- a/asyncgit/src/sync/branch/merge_ff.rs
+++ b/asyncgit/src/sync/branch/merge_ff.rs
@@ -25,7 +25,7 @@ pub fn branch_merge_upstream_fastforward(
let annotated =
repo.find_annotated_commit(upstream_commit.id())?;
- let (analysis, _) = repo.merge_analysis(&[&annotated])?;
+ let (analysis, pref) = repo.merge_analysis(&[&annotated])?;
if !analysis.is_fast_forward() {
return Err(Error::Generic(
@@ -33,6 +33,10 @@ pub fn branch_merge_upstream_fastforward(
));
}
+ if pref.is_no_fast_forward() {
+ return Err(Error::Generic("fast forward not wanted".into()));
+ }
+
//TODO: support merge on unborn
if analysis.is_unborn() {
return Err(Error::Generic("head is unborn".into()));
diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs
new file mode 100644
index 00000000..bb6e5d2f
--- /dev/null
+++ b/asyncgit/src/sync/merge.rs
@@ -0,0 +1,141 @@
+use std::fs::read_to_string;
+
+use crate::{
+ error::{Error, Result},
+ sync::{
+ branch::merge_commit::commit_merge_with_head, reset_stage,
+ reset_workdir, utils, CommitId,
+ },
+};
+use git2::{BranchType, Commit, MergeOptions, Repository};
+use scopetime::scope_time;
+
+///
+pub fn mergehead_ids(repo_path: &str) -> Result<Vec<CommitId>> {
+ scope_time!("mergehead_ids");
+
+ let mut repo = utils::repo(repo_path)?;
+
+ let mut ids: Vec<CommitId> = Vec::new();
+ repo.mergehead_foreach(|id| {
+ ids.push(CommitId::from(*id));
+ true
+ })?;
+
+ Ok(ids)
+}
+
+/// does these steps:
+/// * reset all staged changes,
+/// * revert all changes in workdir
+/// * cleanup repo merge state
+pub fn abort_merge(repo_path: &str) -> Result<()> {
+ scope_time!("cleanup_state");
+
+ let repo = utils::repo(repo_path)?;
+
+ reset_stage(repo_path, "*")?;
+ reset_workdir(repo_path, "*")?;
+
+ repo.cleanup_state()?;
+
+ Ok(())
+}
+
+///
+pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> {
+ scope_time!("merge_branch");
+
+ let repo = utils::repo(repo_path)?;
+
+ merge_branch_repo(&repo, branch)?;
+
+ Ok(())
+}
+
+///
+pub fn merge_branch_repo(
+ repo: &Repository,
+ branch: &str,
+) -> Result<()> {
+ let branch = repo.find_branch(branch, BranchType::Local)?;
+
+ let annotated =
+ repo.reference_to_annotated_commit(&branch.into_reference())?;
+
+ let (analysis, _) = repo.merge_analysis(&[&annotated])?;
+
+ //TODO: support merge on unborn
+ if analysis.is_unborn() {
+ return Err(Error::Generic("head is unborn".into()));
+ }
+
+ let mut opt = MergeOptions::default();
+
+ repo.merge(&[&annotated], Some(&mut opt), None)?;
+
+ Ok(())
+}
+
+///
+pub fn merge_msg(repo_path: &str) -> Result<String> {
+ scope_time!("merge_msg");
+
+ let repo = utils::repo(repo_path)?;
+
+ let msg_file = repo.path().join("MERGE_MSG");
+
+ let content = read_to_string(msg_file).unwrap_or_default();
+
+ Ok(content)
+}
+
+///
+pub fn merge_commit(
+ repo_path: &str,
+ msg: &str,
+ ids: &[CommitId],
+) -> Result<CommitId> {
+ scope_time!("merge_commit");
+
+ let repo = utils::repo(repo_path)?;
+
+ let mut commits: Vec<Commit> = Vec::new();
+
+ for id in ids {
+ commits.push(repo.find_commit((*id).into())?);
+ }
+
+ let id = commit_merge_with_head(&repo, &commits, msg)?;
+
+ Ok(id)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::sync::{
+ create_branch,
+ tests::{repo_init, write_commit_file},
+ };
+
+ #[test]
+ fn test_smoke() {
+ let (_td, repo) = repo_init().unwrap();
+ let root = repo.path().parent().unwrap();
+ let repo_path = root.as_os_str().to_str().unwrap();
+
+ let c1 =
+ write_commit_file(&repo, "test.txt", "test", "commit1");
+
+ create_branch(repo_path, "foo").unwrap();
+
+ write_commit_file(&repo, "test.txt", "test2", "commit2");
+
+ merge_branch(repo_path, "master").unwrap();
+
+ let mergeheads = mergehead_ids(repo_path).unwrap();
+
+ assert_eq!(mergeheads[0], c1);
+ }
+}
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index 1bcba4b8..838724d6 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -15,6 +15,7 @@ mod hooks;
mod hunks;
mod ignore;
mod logwalker;
+mod merge;
mod patches;
pub mod remotes;
mod reset;
@@ -49,6 +50,9 @@ pub use hooks::{
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
pub use ignore::add_to_ignore;
pub use logwalker::LogWalker;
+pub use merge::{
+ abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids,
+};
pub use remotes::{
get_default_remote, get_remotes, push::AsyncProgress,
tags::PushTagsProgress,
diff --git a/src/app.rs b/src/app.rs
index 801f0105..9ce52a65 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -475,6 +475,9 @@ impl App {
if flags.contains(NeedsUpdate::COMMANDS) {
self.update_commands();
}
+ if flags.contains(NeedsUpdate::BRANCHES) {
+ self.select_branch_popup.update_branches()?;
+ }
Ok(())
}
@@ -607,6 +610,10 @@ impl App {
self.pull_popup.try_conflict_free_merge(rebase);
flags.insert(NeedsUpdate::ALL);
}
+ Action::AbortMerge => {
+ self.status_tab.abort_merge();
+ flags.insert(NeedsUpdate::ALL);
+ }
};
Ok(())
diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs
index de41295c..7db47b4f 100644
--- a/src/components/branchlist.rs
+++ b/src/components/branchlist.rs
@@ -12,7 +12,7 @@ use crate::{
use anyhow::Result;
use asyncgit::{
sync::{
- branch::checkout_remote_branch, checkout_branch,
+ self, branch::checkout_remote_branch, checkout_branch,
get_branches_info, BranchInfo,
},
CWD,
@@ -151,6 +151,14 @@ impl Component for BranchListComponent {
));
out.push(CommandInfo::new(
+ strings::commands::merge_branch_popup(
+ &self.key_config,
+ ),
+ !self.selection_is_cur_branch(),
+ self.local,
+ ));
+
+ out.push(CommandInfo::new(
strings::commands::rename_branch_popup(
&self.key_config,
),
@@ -194,7 +202,6 @@ impl Component for BranchListComponent {
self.queue
.borrow_mut()
.push_back(InternalEvent::CreateBranch);
- self.hide();
} else if e == self.key_config.rename_branch
&& self.valid_selection()
{
@@ -222,6 +229,19 @@ impl Component for BranchListComponent {
),
),
);
+ } else if e == self.key_config.merge_branch
+ && !self.selection_is_cur_branch()
+ && self.valid_selection()
+ {
+ try_or_popup!(
+ self,
+ "merge branch error:",
+ self.merge_branch()
+ );
+ self.hide();
+ self.queue.borrow_mut().push_back(
+ InternalEvent::Update(NeedsUpdate::ALL),
+ );
} else if e == self.key_config.tab_toggle {
self.local = !self.local;
self.update_branches()?;
@@ -294,6 +314,16 @@ impl BranchListComponent {
!self.branches.is_empty()
}
+ fn merge_branch(&self) -> Result<()> {
+ if let Some(branch) =
+ self.branches.get(usize::from(self.selection))
+ {
+ sync::merge_branch(CWD, &branch.name)?;
+ }
+
+ Ok(())
+ }
+
fn selection_is_cur_branch(&self) -> bool {
self.branches
.iter()
diff --git a/src/components/commit.rs b/src/components/commit.rs
index c7b7efea..49ff4df7 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -13,7 +13,10 @@ use crate::{
use anyhow::Result;
use asyncgit::{
cached,
- sync::{self, utils::get_config_string, CommitId, HookResult},
+ sync::{
+ self, utils::get_config_string, CommitId, HookResult,
+ RepoState,
+ },
CWD,
};
use crossterm::event::Event;
@@ -30,9 +33,15 @@ use tui::{
Frame,
};
+enum Mode {
+ Normal,
+ Amend(CommitId),
+ Merge(Vec<CommitId>),
+}
+
pub struct CommitComponent {
input: TextInputComponent,
- amend: Option<CommitId>,
+ mode: Mode,
queue: Queue,
key_config: SharedKeyConfig,
git_branch_name: cached::BranchName,
@@ -128,25 +137,34 @@ impl Component for CommitComponent {
}
fn show(&mut self) -> Result<()> {
- if self.amend.is_some() {
+ //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited
+ if !matches!(self.mode, Mode::Normal) {
self.input.clear();
}
- self.amend = None;
-
- self.input
- .set_title(strings::commit_title(&self.key_config));
- self.commit_template =
- get_config_string(CWD, "commit.template")
- .ok()
- .flatten()
- .and_then(|path| read_to_string(path).ok());
-
- if self.is_empty() {
- if let Some(s) = &self.commit_template {
- self.input.set_text(s.clone());
+ self.mode = Mode::Normal;
+
+ self.mode = if sync::repo_state(CWD)? == RepoState::Merge {
+ let ids = sync::mergehead_ids(CWD)?;
+ self.input.set_title(strings::commit_title_merge());
+ self.input.set_text(sync::merge_msg(CWD)?);
+ Mode::Merge(ids)
+ } else {
+ self.commit_template =
+ get_config_string(CWD, "commit.template")
+ .ok()
+ .flatten()
+ .and_then(|path| read_to_string(path).ok());
+
+ if self.is_empty() {
+ if let Some(s) = &self.commit_template {
+ self.input.set_text(s.clone());
+ }
}
- }
+
+ self.input.set_title(strings::commit_title());
+ Mode::Normal
+ };
self.input.show()?;
@@ -163,7 +181,8 @@ impl CommitComponent {
) -> Self {
Self {
queue,
- amend: None,
+ mode: Mode::Normal,
+
input: TextInputComponent::new(
theme.clone(),
key_config.clone(),
@@ -281,10 +300,10 @@ impl CommitComponent {
fn commit(&mut self) -> Result<()> {
let msg = self.input.get_text().clone();
self.input.clear();
- self.commit_msg(msg)
+ self.commit_with_msg(msg)
}
- fn commit_msg(&mut self, msg: String) -> Result<()> {
+ fn commit_with_msg(&mut self, msg: String) -> Result<()> {
if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? {
log::error!("pre-commit hook error: {}", e);
self.queue.borrow_mut().push_back(
@@ -309,10 +328,12 @@ impl CommitComponent {
return Ok(());
}
- let res = self.amend.map_or_else(
- || sync::commit(CWD, &msg),
- |amend| sync::amend(CWD, amend, &msg),
- );
+ let res = match &self.mode {
+ Mode::Normal => sync::commit(CWD, &msg),
+ Mode::Amend(amend) => sync::amend(CWD, *amend, &msg),
+ Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids),
+ };
+
if let Err(e) = res {
log::error!("commit error: {}", &e);
self.queue.borrow_mut().push_back(
@@ -348,7 +369,7 @@ impl CommitComponent {
}
fn can_amend(&self) -> bool {
- self.amend.is_none()
+ matches!(self.mode, Mode::Normal)
&& sync::get_head(CWD).is_ok()
&& (self.is_empty() || !self.is_changed())
}
@@ -363,16 +384,19 @@ impl CommitComponent {
}
fn amend(&mut self) -> Result<()> {
- let id = sync::get_head(CWD)?;
- self.amend = Some(id);
+ if self.can_amend() {
+ let id = sync::get_head(CWD)?;
+ self.mode = Mode::Amend(id);
- let details = sync::get_commit_details(CWD, id)?;
+ let details = sync::get_commit_details(CWD, id)?;
- self.input
- .set_title(strings::commit_title_amend(&self.key_config));
+ self.input.set_title(strings::commit_title_amend(
+ &self.key_config,
+ ));
- if let Some(msg) = details.message {
- self.input.set_text(msg.combine());
+ if let Some(msg) = details.message {
+ self.input.set_text(msg.combine());
+ }
}
Ok(())
diff --git a/src/components/create_branch.rs b/src/components/create_branch.rs
index d74b1ec7..f3c77ebb 100644
--- a/src/components/create_branch.rs
+++ b/src/components/create_branch.rs
@@ -123,7 +123,7 @@ impl CreateBranchComponent {
match res {
Ok(_) => {
self.queue.borrow_mut().push_back(
- InternalEvent::Update(NeedsUpdate::ALL),
+ InternalEvent::Update(NeedsUpdate::BRANCHES),
);
}
Err(e) => {
diff --git a/src/components/reset.rs b/src/components/reset.rs
index eeef90d1..2004545f 100644
--- a/src/components/reset.rs
+++ b/src/components/reset.rs
@@ -138,8 +138,8 @@ impl ResetComponent {
if let Some(ref a) = self.target {
return match a {
Action::Reset(_) => (
- strings::confirm_title_reset(&self.key_config),</