diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 1036b5ec95..d30f32dd84 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -11,8 +11,8 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-
     runs-on: ${{ matrix.os }}
+    
     steps:
     - uses: actions/checkout@v2
 
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1607fbce55..76178a8b83 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,6 @@ jobs:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
         rust: [nightly, stable]
-
     runs-on: ${{ matrix.os }}
     continue-on-error: ${{ matrix.rust == 'nightly' }}
 
@@ -54,14 +53,13 @@ jobs:
   build-linux-musl:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@master
     - name: Install Rust
       uses: actions-rs/toolchain@v1
       with:
         toolchain: stable
         profile: minimal
         target: x86_64-unknown-linux-musl
-
     - name: Setup MUSL
       run: |
         sudo apt-get -qq install musl-tools
@@ -73,6 +71,9 @@ jobs:
       run: |
         make build-linux-musl-release
         ./target/x86_64-unknown-linux-musl/release/gitui --version
+    - name: Test
+      run: |
+        make test-linux-musl
 
   rustfmt:
     name: Rustfmt
diff --git a/Cargo.lock b/Cargo.lock
index ef27c69104..a468f93c1c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -414,6 +414,8 @@ dependencies = [
  "libc",
  "libgit2-sys",
  "log",
+ "openssl-probe",
+ "openssl-sys",
  "url",
 ]
 
@@ -552,10 +554,26 @@ checksum = "0100ae90655025134424939f1f60e27e879460d451dff6afedde4f8226cbebfc"
 dependencies = [
  "cc",
  "libc",
+ "libssh2-sys",
  "libz-sys",
+ "openssl-sys",
  "pkg-config",
 ]
 
+[[package]]
+name = "libssh2-sys"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "libz-sys"
 version = "1.1.0"
@@ -785,6 +803,35 @@ version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
 
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
+name = "openssl-src"
+version = "111.10.2+1.1.1g"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a287fdb22e32b5b60624d4a5a7a02dbe82777f730ec0dbc42a0554326fef5a70"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "parking_lot"
 version = "0.10.2"
diff --git a/Makefile b/Makefile
index fa537a24bf..ac0688366c 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,9 @@ build-linux-musl-debug:
 build-linux-musl-release:
 	cargo build --release --target=x86_64-unknown-linux-musl --no-default-features
 
+test-linux-musl:
+	cargo test --workspace --target=x86_64-unknown-linux-musl --no-default-features
+
 test:
 	cargo test --workspace
 
diff --git a/assets/vim_style_key_config.ron b/assets/vim_style_key_config.ron
index 129c76e630..6e4735b23d 100644
--- a/assets/vim_style_key_config.ron
+++ b/assets/vim_style_key_config.ron
@@ -59,4 +59,5 @@
     commit_amend: ( code: Char('A'), modifiers: ( bits: 0,),),
     copy: ( code: Char('y'), modifiers: ( bits: 0,),),
     create_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
+    push: ( code: Char('p'), modifiers: ( bits: 0,),),
 )
diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml
index e0c22963b6..621f07ebcb 100644
--- a/asyncgit/Cargo.toml
+++ b/asyncgit/Cargo.toml
@@ -13,7 +13,7 @@ keywords = ["git"]
 
 [dependencies]
 scopetime = { path = "../scopetime", version = "0.1" }
-git2 = { version = "0.13.10", default-features = false }
+git2 = { version = "0.13", features = ["vendored-openssl"] }
 rayon-core = "1.8"
 crossbeam-channel = "0.4"
 log = "0.4"
diff --git a/asyncgit/src/cached/branchname.rs b/asyncgit/src/cached/branchname.rs
index e5df31aa7b..8464458aa7 100644
--- a/asyncgit/src/cached/branchname.rs
+++ b/asyncgit/src/cached/branchname.rs
@@ -32,6 +32,11 @@ impl BranchName {
         self.fetch(current_head)
     }
 
+    ///
+    pub fn last(&self) -> Option<String> {
+        self.last_result.as_ref().map(|last| last.1.clone())
+    }
+
     fn fetch(&mut self, head: Head) -> Result<String> {
         let name = sync::get_branch_name(self.repo_path.as_str())?;
         self.last_result = Some((head, name.clone()));
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index 58b32148be..4eb97b6967 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -10,6 +10,7 @@ mod hooks;
 mod hunks;
 mod ignore;
 mod logwalker;
+mod remotes;
 mod reset;
 mod stash;
 pub mod status;
@@ -29,6 +30,9 @@ pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
 pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
 pub use ignore::add_to_ignore;
 pub use logwalker::LogWalker;
+pub use remotes::{
+    fetch_origin, get_remotes, push_origin, remote_push_master,
+};
 pub use reset::{reset_stage, reset_workdir};
 pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
 pub use tags::{get_tags, CommitTags, Tags};
diff --git a/asyncgit/src/sync/remotes.rs b/asyncgit/src/sync/remotes.rs
new file mode 100644
index 0000000000..916b4f876d
--- /dev/null
+++ b/asyncgit/src/sync/remotes.rs
@@ -0,0 +1,103 @@
+//!
+
+use crate::{error::Result, sync::utils};
+use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks};
+use scopetime::scope_time;
+
+///
+pub fn get_remotes(repo_path: &str) -> Result<Vec<String>> {
+    scope_time!("get_remotes");
+
+    let repo = utils::repo(repo_path)?;
+    let remotes = repo.remotes()?;
+    let remotes: Vec<String> =
+        remotes.iter().filter_map(|s| s).map(String::from).collect();
+
+    Ok(remotes)
+}
+
+///
+pub fn remote_push_master(repo_path: &str) -> Result<()> {
+    scope_time!("remote_push_master");
+
+    let repo = utils::repo(repo_path)?;
+    let mut remote = repo.find_remote("origin")?;
+
+    remote.push(&["refs/heads/master"], None)?;
+
+    Ok(())
+}
+
+///
+pub fn fetch_origin(repo_path: &str, branch: &str) -> Result<usize> {
+    scope_time!("remote_fetch_master");
+
+    let repo = utils::repo(repo_path)?;
+    let mut remote = repo.find_remote("origin")?;
+
+    let mut options = FetchOptions::new();
+    options.remote_callbacks(remote_callbacks());
+
+    remote.fetch(&[branch], Some(&mut options), None)?;
+
+    Ok(remote.stats().received_bytes())
+}
+
+///
+pub fn push_origin(repo_path: &str, branch: &str) -> Result<()> {
+    scope_time!("push_origin");
+
+    let repo = utils::repo(repo_path)?;
+    let mut remote = repo.find_remote("origin")?;
+
+    let mut options = PushOptions::new();
+    options.remote_callbacks(remote_callbacks());
+
+    remote.push(&[branch], Some(&mut options))?;
+
+    Ok(())
+}
+
+fn remote_callbacks<'a>() -> RemoteCallbacks<'a> {
+    let mut callbacks = RemoteCallbacks::new();
+    callbacks.credentials(|url, username_from_url, allowed_types| {
+        log::debug!(
+            "creds: '{}' {:?} ({:?})",
+            url,
+            username_from_url,
+            allowed_types
+        );
+
+        Cred::ssh_key_from_agent(
+            username_from_url.expect("username not found"),
+        )
+    });
+
+    callbacks
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::sync::tests::debug_cmd_print;
+    use tempfile::TempDir;
+
+    #[test]
+    fn test_smoke() {
+        let td = TempDir::new().unwrap();
+
+        debug_cmd_print(
+            td.path().as_os_str().to_str().unwrap(),
+            "git clone https://github.com/extrawurst/brewdump.git",
+        );
+
+        let repo_path = td.path().join("brewdump");
+        let repo_path = repo_path.as_os_str().to_str().unwrap();
+
+        let remotes = get_remotes(repo_path).unwrap();
+
+        assert_eq!(remotes, vec![String::from("origin")]);
+
+        fetch_origin(repo_path, "master").unwrap();
+    }
+}
diff --git a/src/app.rs b/src/app.rs
index eb7958ff5a..adb6f1e411 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -256,7 +256,7 @@ impl App {
                     let msg =
                         format!("failed to launch editor:\n{}", e);
                     log::error!("{}", msg.as_str());
-                    self.msg.show_msg(msg.as_str())?;
+                    self.msg.show_error(msg.as_str())?;
                 }
 
                 self.requires_redraw.set(true);
@@ -454,7 +454,12 @@ impl App {
                 flags.insert(NeedsUpdate::COMMANDS);
             }
             InternalEvent::ShowErrorMsg(msg) => {
-                self.msg.show_msg(msg.as_str())?;
+                self.msg.show_error(msg.as_str())?;
+                flags
+                    .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
+            }
+            InternalEvent::ShowInfoMsg(msg) => {
+                self.msg.show_info(msg.as_str())?;
                 flags
                     .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
             }
diff --git a/src/clipboard.rs b/src/clipboard.rs
index b0c3e42ff4..2828ff6577 100644
--- a/src/clipboard.rs
+++ b/src/clipboard.rs
@@ -20,7 +20,7 @@ pub fn copy_string(_string: String) -> Result<()> {
 }
 
 #[cfg(feature = "clipboard")]
-pub fn is_supported() -> bool {
+pub const fn is_supported() -> bool {
     true
 }
 
diff --git a/src/components/changes.rs b/src/components/changes.rs
index 396afda99e..acdf5e3604 100644
--- a/src/components/changes.rs
+++ b/src/components/changes.rs
@@ -64,6 +64,11 @@ impl ChangesComponent {
         Ok(())
     }
 
+    ///
+    pub fn branch_name(&self) -> Option<String> {
+        self.branch_name.last()
+    }
+
     ///
     pub fn set_items(&mut self, list: &[StatusItem]) -> Result<()> {
         self.files.update(list)?;
diff --git a/src/components/msg.rs b/src/components/msg.rs
index 6320b3f610..d3c6e87bc0 100644
--- a/src/components/msg.rs
+++ b/src/components/msg.rs
@@ -14,6 +14,7 @@ use tui::{
 use ui::style::SharedTheme;
 
 pub struct MsgComponent {
+    title: String,
     msg: String,
     visible: bool,
     theme: SharedTheme,
@@ -39,9 +40,7 @@ impl DrawableComponent for MsgComponent {
             Paragraph::new(txt.iter())
                 .block(
                     Block::default()
-                        .title(&strings::msg_title_error(
-                            &self.key_config,
-                        ))
+                        .title(self.title.as_str())
                         .title_style(self.theme.text_danger())
                         .borders(Borders::ALL)
                         .border_type(BorderType::Thick),
@@ -104,14 +103,26 @@ impl MsgComponent {
         key_config: SharedKeyConfig,
     ) -> Self {
         Self {
+            title: String::new(),
             msg: String::new(),
             visible: false,
             theme,
             key_config,
         }
     }
+
+    ///
+    pub fn show_error(&mut self, msg: &str) -> Result<()> {
+        self.title = strings::msg_title_error(&self.key_config);
+        self.msg = msg.to_string();
+        self.show()?;
+
+        Ok(())
+    }
+
     ///
-    pub fn show_msg(&mut self, msg: &str) -> Result<()> {
+    pub fn show_info(&mut self, msg: &str) -> Result<()> {
+        self.title = strings::msg_title_info(&self.key_config);
         self.msg = msg.to_string();
         self.show()?;
 
diff --git a/src/keys.rs b/src/keys.rs
index 41c1e7ffd5..24d0ea1ee9 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -60,6 +60,7 @@ pub struct KeyConfig {
     pub commit_amend: KeyEvent,
     pub copy: KeyEvent,
     pub create_branch: KeyEvent,
+    pub push: KeyEvent,
 }
 
 #[rustfmt::skip]
@@ -109,6 +110,7 @@ impl Default for KeyConfig {
 			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('b'), modifiers: KeyModifiers::empty()},
+            push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
         }
     }
 }
diff --git a/src/queue.rs b/src/queue.rs
index 3e3478f3ff..982584890c 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -39,6 +39,8 @@ pub enum InternalEvent {
     ///
     ShowErrorMsg(String),
     ///
+    ShowInfoMsg(String),
+    ///
     Update(NeedsUpdate),
     /// open commit msg input
     OpenCommit,
diff --git a/src/strings.rs b/src/strings.rs
index 077708777d..4f8da04734 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -40,6 +40,9 @@ pub fn msg_opening_editor(_key_config: &SharedKeyConfig) -> String {
 pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String {
     "Error".to_string()
 }
+pub fn msg_title_info(_key_config: &SharedKeyConfig) -> String {
+    "Info".to_string()
+}
 pub fn commit_title(_key_config: &SharedKeyConfig) -> String {
     "Commit".to_string()
 }
@@ -596,4 +599,11 @@ pub mod commands {
             CMD_GROUP_GENERAL,
         )
     }
+    pub fn status_push(key_config: &SharedKeyConfig) -> CommandText {
+        CommandText::new(
+            format!("Push [{}]", get_hint(key_config.push),),
+            "push to origin",
+            CMD_GROUP_GENERAL,
+        )
+    }
 }
diff --git a/src/tabs/status.rs b/src/tabs/status.rs
index 120f159ac1..69ba6c79da 100644
--- a/src/tabs/status.rs
+++ b/src/tabs/status.rs
@@ -323,6 +323,24 @@ impl Status {
             true
         }
     }
+
+    fn push(&self) {
+        if let Some(branch) = self.index_wd.branch_name() {
+            let branch = format!("refs/heads/{}", branch);
+            if let Err(e) = sync::push_origin(CWD, branch.as_str()) {
+                self.queue.borrow_mut().push_back(
+                    InternalEvent::ShowErrorMsg(format!(
+                        "push failed:\n{}",
+                        e
+                    )),
+                );
+            } else {
+                self.queue.borrow_mut().push_back(
+                    InternalEvent::ShowInfoMsg("pushed".to_string()),
+                );
+            }
+        }
+    }
 }
 
 impl Component for Status {
@@ -369,6 +387,11 @@ impl Component for Status {
             true,
             true,
         ));
+        out.push(CommandInfo::new(
+            strings::commands::status_push(&self.key_config),
+            self.index_wd.branch_name().is_some(),
+            true,
+        ));
 
         out.push(
             CommandInfo::new(
@@ -451,6 +474,9 @@ impl Component for Status {
                         .borrow_mut()
                         .push_back(InternalEvent::CreateBranch);
                     Ok(true)
+                } else if k == self.key_config.push {
+                    self.push();
+                    Ok(true)
                 } else {
                     Ok(false)
                 };