diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs
new file mode 100644
index 0000000000..c61e7411fa
--- /dev/null
+++ b/asyncgit/src/sync/merge.rs
@@ -0,0 +1,49 @@
+use crate::{
+    error::{Error, Result},
+    sync::{reset_stage, reset_workdir, utils},
+};
+use git2::{BranchType, MergeOptions};
+use scopetime::scope_time;
+
+/// 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)?;
+
+    let branch = repo.find_branch(branch, BranchType::Local)?;
+
+    let id = branch.into_reference().peel_to_commit()?;
+
+    let annotated = repo.find_annotated_commit(id.id())?;
+
+    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(())
+}
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index 1bcba4b860..7bb41c95f2 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,7 @@ 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};
 pub use remotes::{
     get_default_remote, get_remotes, push::AsyncProgress,
     tags::PushTagsProgress,
diff --git a/src/app.rs b/src/app.rs
index 801f01052d..0daa984a32 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -607,6 +607,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 de41295c50..c79a22e407 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,
@@ -150,6 +150,14 @@ impl Component for BranchListComponent {
                 self.local,
             ));
 
+            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,
@@ -222,6 +230,16 @@ 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();
                 } else if e == self.key_config.tab_toggle {
                     self.local = !self.local;
                     self.update_branches()?;
@@ -294,6 +312,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/reset.rs b/src/components/reset.rs
index eeef90d1ea..2004545fab 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),
-                    strings::confirm_msg_reset(&self.key_config),
+                    strings::confirm_title_reset(),
+                    strings::confirm_msg_reset(),
                 ),
                 Action::StashDrop(_) => (
                     strings::confirm_title_stashdrop(
@@ -152,12 +152,12 @@ impl ResetComponent {
                     strings::confirm_msg_stashpop(&self.key_config),
                 ),
                 Action::ResetHunk(_, _) => (
-                    strings::confirm_title_reset(&self.key_config),
+                    strings::confirm_title_reset(),
                     strings::confirm_msg_resethunk(&self.key_config),
                 ),
                 Action::ResetLines(_, lines) => (
-                    strings::confirm_title_reset(&self.key_config),
-                    strings::confirm_msg_reset_lines(&self.key_config,lines.len()),
+                    strings::confirm_title_reset(),
+                    strings::confirm_msg_reset_lines(lines.len()),
                 ),
                 Action::DeleteBranch(branch_ref) => (
                     strings::confirm_title_delete_branch(
@@ -181,6 +181,10 @@ impl ResetComponent {
                     strings::confirm_title_merge(&self.key_config,*rebase),
                     strings::confirm_msg_merge(&self.key_config,*incoming,*rebase),
                 ),
+                Action::AbortMerge => (
+                    strings::confirm_title_abortmerge(),
+                    strings::confirm_msg_abortmerge(),
+                ),
             };
         }
 
diff --git a/src/keys.rs b/src/keys.rs
index 168ed72cdf..6ee72ea9f5 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -68,9 +68,11 @@ pub struct KeyConfig {
     pub rename_branch: KeyEvent,
     pub select_branch: KeyEvent,
     pub delete_branch: KeyEvent,
+    pub merge_branch: KeyEvent,
     pub push: KeyEvent,
     pub force_push: KeyEvent,
     pub pull: KeyEvent,
+    pub abort_merge: KeyEvent,
 }
 
 #[rustfmt::skip]
@@ -121,13 +123,15 @@ impl Default for KeyConfig {
 			log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()},
 			commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
             copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
-            create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE},
-            rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::NONE},
-            select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::NONE},
+            create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()},
+            rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty()},
+            select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
             delete_branch: KeyEvent{code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
+            merge_branch: KeyEvent{code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
             push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
             force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
             pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
+            abort_merge: KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT},
         }
     }
 }
diff --git a/src/queue.rs b/src/queue.rs
index 131de06593..a5226e51f5 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -33,6 +33,7 @@ pub enum Action {
     DeleteBranch(String),
     ForcePush(String, bool),
     PullMerge { incoming: usize, rebase: bool },
+    AbortMerge,
 }
 
 ///
diff --git a/src/strings.rs b/src/strings.rs
index 01ce8faa16..c52298a4db 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -86,7 +86,7 @@ pub fn stash_popup_title(_key_config: &SharedKeyConfig) -> String {
 pub fn stash_popup_msg(_key_config: &SharedKeyConfig) -> String {
     "type name (optional)".to_string()
 }
-pub fn confirm_title_reset(_key_config: &SharedKeyConfig) -> String {
+pub fn confirm_title_reset() -> String {
     "Reset".to_string()
 }
 pub fn confirm_title_stashdrop(
@@ -120,13 +120,17 @@ pub fn confirm_msg_merge(
         format!("Merge of {} incoming commits?", incoming)
     }
 }
-pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String {
+
+pub fn confirm_title_abortmerge() -> String {
+    "Abort merge?".to_string()
+}
+pub fn confirm_msg_abortmerge() -> String {
+    "This will revert all changes. Are you sure?".to_string()
+}
+pub fn confirm_msg_reset() -> String {
     "confirm file reset?".to_string()
 }
-pub fn confirm_msg_reset_lines(
-    _key_config: &SharedKeyConfig,
-    lines: usize,
-) -> String {
+pub fn confirm_msg_reset_lines(lines: usize) -> String {
     format!(
         "are you sure you want to discard {} selected lines?",
         lines
@@ -520,6 +524,16 @@ pub mod commands {
             CMD_GROUP_GENERAL,
         )
     }
+    pub fn abort_merge(key_config: &SharedKeyConfig) -> CommandText {
+        CommandText::new(
+            format!(
+                "Abort merge [{}]",
+                key_config.get_hint(key_config.abort_merge),
+            ),
+            "abort ongoing merge",
+            CMD_GROUP_GENERAL,
+        )
+    }
     pub fn select_staging(
         key_config: &SharedKeyConfig,
     ) -> CommandText {
@@ -918,6 +932,18 @@ pub mod commands {
             CMD_GROUP_GENERAL,
         )
     }
+    pub fn merge_branch_popup(
+        key_config: &SharedKeyConfig,
+    ) -> CommandText {
+        CommandText::new(
+            format!(
+                "Merge [{}]",
+                key_config.get_hint(key_config.merge_branch),
+            ),
+            "merge a branch",
+            CMD_GROUP_GENERAL,
+        )
+    }
     pub fn select_branch_popup(
         key_config: &SharedKeyConfig,
     ) -> CommandText {
diff --git a/src/tabs/status.rs b/src/tabs/status.rs
index bb5f285a1e..cccca38321 100644
--- a/src/tabs/status.rs
+++ b/src/tabs/status.rs
@@ -8,7 +8,7 @@ use crate::{
     },
     keys::SharedKeyConfig,
     queue::{Action, InternalEvent, Queue, ResetItem},
-    strings,
+    strings, try_or_popup,
     ui::style::SharedTheme,
 };
 use anyhow::Result;
@@ -465,6 +465,61 @@ impl Status {
             .as_ref()
             .map_or(true, |state| state.ahead > 0)
     }
+
+    fn can_abort_merge() -> bool {
+        sync::repo_state(CWD).unwrap_or(RepoState::Clean)
+            == RepoState::Merge
+    }
+
+    pub fn abort_merge(&self) {
+        try_or_popup!(self, "abort merge", sync::abort_merge(CWD))
+    }
+
+    fn commands_nav(
+        &self,
+        out: &mut Vec<CommandInfo>,
+        force_all: bool,
+    ) {
+        let focus_on_diff = self.is_focus_on_diff();
+        out.push(
+            CommandInfo::new(
+                strings::commands::diff_focus_left(&self.key_config),
+                true,
+                (self.visible && focus_on_diff) || force_all,
+            )
+            .order(strings::order::NAV),
+        );
+        out.push(
+            CommandInfo::new(
+                strings::commands::diff_focus_right(&self.key_config),
+                self.can_focus_diff(),
+                (self.visible && !focus_on_diff) || force_all,
+            )
+            .order(strings::order::NAV),
+        );
+        out.push(
+            CommandInfo::new(
+                strings::commands::select_staging(&self.key_config),
+                !focus_on_diff,
+                (self.visible
+                    && !focus_on_diff
+                    && self.focus == Focus::WorkDir)
+                    || force_all,
+            )
+            .order(strings::order::NAV),
+        );
+        out.push(
+            CommandInfo::new(
+                strings::commands::select_unstaged(&self.key_config),
+                !focus_on_diff,
+                (self.visible
+                    && !focus_on_diff
+                    && self.focus == Focus::Stage)
+                    || force_all,
+            )
+            .order(strings::order::NAV),
+        );
+    }
 }
 
 impl Component for Status {
@@ -507,6 +562,12 @@ impl Component for Status {
                 true,
                 !focus_on_diff,
             ));
+
+            out.push(CommandInfo::new(
+                strings::commands::abort_merge(&self.key_config),
+                true,
+                Self::can_abort_merge() || force_all,
+            ));
         }
 
         {
@@ -519,52 +580,6 @@ impl Component for Status {
                 },
                 self.visible || force_all,
             ));
-            out.push(
-                CommandInfo::new(
-                    strings::commands::diff_focus_left(
-                        &self.key_config,
-                    ),
-                    true,
-                    (self.visible && focus_on_diff) || force_all,
-                )
-                .order(strings::order::NAV),
-            );
-            out.push(
-                CommandInfo::new(
-                    strings::commands::diff_focus_right(
-                        &self.key_config,
-                    ),
-                    self.can_focus_diff(),
-                    (self.visible && !focus_on_diff) || force_all,
-                )
-                .order(strings::order::NAV),
-            );
-            out.push(
-                CommandInfo::new(
-                    strings::commands::select_staging(
-                        &self.key_config,
-                    ),
-                    !focus_on_diff,
-                    (self.visible
-                        && !focus_on_diff
-                        && self.focus == Focus::WorkDir)
-                        || force_all,
-                )
-                .order(strings::order::NAV),
-            );
-            out.push(
-                CommandInfo::new(
-                    strings::commands::select_unstaged(
-                        &self.key_config,
-                    ),
-                    !focus_on_diff,
-                    (self.visible
-                        && !focus_on_diff
-                        && self.focus == Focus::Stage)
-                        || force_all,
-                )
-                .order(strings::order::NAV),
-            );
 
             out.push(
                 CommandInfo::new(
@@ -576,6 +591,8 @@ impl Component for Status {
                 )
                 .hidden(),
             );
+
+            self.commands_nav(out, force_all);
         }
 
         visibility_blocking(self)
@@ -653,6 +670,16 @@ impl Component for Status {
                     && !self.is_focus_on_diff()
                 {
                     self.pull();
+                    Ok(EventState::Consumed)
+                } else if k == self.key_config.abort_merge
+                    && Self::can_abort_merge()
+                {
+                    self.queue.borrow_mut().push_back(
+                        InternalEvent::ConfirmAction(
+                            Action::AbortMerge,
+                        ),
+                    );
+
                     Ok(EventState::Consumed)
                 } else {
                     Ok(EventState::NotConsumed)
diff --git a/vim_style_key_config.ron b/vim_style_key_config.ron
index abdf498631..0a64ae4daa 100644
--- a/vim_style_key_config.ron
+++ b/vim_style_key_config.ron
@@ -72,6 +72,9 @@
     rename_branch: ( code: Char('r'), modifiers: ( bits: 0,),),
     select_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
     delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),),
+    merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),),
+    abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),),
+
     push: ( code: Char('p'), modifiers: ( bits: 0,),),
     force_push: ( code: Char('P'), modifiers: ( bits: 1,),),
     pull: ( code: Char('f'), modifiers: ( bits: 0,),),