Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emacs -- How to push a Git repository to multiple remotes

Tags:

emacs

elisp

magit

I'm looking for some assistance, please, using Emacs / Magit to push the local repository changes to the remote website and to Github in one fell-swoop.

I found a non-Emacs / non-Magit related thread ( https://stackoverflow.com/a/3195446/2112489) , with comments stating that it is the definitive answer on pushing to a remote and to Github, and it has a few hundred thumbs-up. I assume (perhaps incorrectly) that is a good starting point for the local .gitconfig file in the $HOME directory on my computer.

[remote "GitHub"]
    url = [email protected]:elliottcable/Paws.o.git
    fetch = +refs/heads/*:refs/remotes/GitHub/*
[branch "Master"]
    remote = GitHub
    merge = refs/heads/Master
[remote "Codaset"]
    url = [email protected]:elliottcable/paws-o.git
    fetch = +refs/heads/*:refs/remotes/Codaset/*
[remote "Paws"]
    url = [email protected]:Paws/Paws.o.git
    fetch = +refs/heads/*:refs/remotes/Paws/*

The basic Push command in Emacs / Magit only pushes one at a time:

C-u P P [and then use arrow keys to select from the choices in the minibuffer] RET

See the Magit cheatsheet of available commands: http://daemianmack.com/magit-cheatsheet.html


Tentative thinking -- use /usr/local/git/bin/git remote -v to obtain a listing of remotes that have already been configured, and then use the results to push to each one . . . doable, but complex.

$ MP:my_project.git HOME$ /usr/local/git/bin/git remote -v

  origin    [email protected]:lawlist/my_project.git (fetch)
  origin    [email protected]:lawlist/my_project.git (push)
  remote_website    [email protected]:my_project.git (fetch)
  remote_website    [email protected]:my_project.git (push)

COMMAND-LINE RECIPE -- pushing separately to the remote and to Github:

;; Setup the remote repository and the hook; and the remote destination folder.
ssh [email protected]
mkdir /home/lawlist/my_project.git
cd my_project.git
git init --bare
;; git update-server-info # If planning to serve via HTTP
cat > /home/lawlist/my_project.git/hooks/post-receive ;; RET
#!/bin/sh ;; RET
GIT_WORK_TREE=/home/lawlist/my_project git checkout -f ;; RET
;; C-d
chmod 755 /home/lawlist/my_project.git/hooks/post-receive
mkdir /home/lawlist/my_project
exit

;; On local machine.
mkdir /Users/HOME/.0.data/.0.emacs/elpa/my_project.git
touch /Users/HOME/.0.data/.0.emacs/elpa/my_project.git/README.md
cd /Users/HOME/.0.data/.0.emacs/elpa/my_project.git
/usr/local/git/bin/git init
/usr/local/git/bin/git add .
/usr/local/git/bin/git commit -m "First commit."
curl -u lawlist:12345678 https://api.github.com/user/repos -d '{"name":"my_project.git"}'
/usr/local/git/bin/git remote add origin [email protected]:lawlist/my_project.git
/usr/local/git/bin/git remote add remote_website [email protected]:my_project.git
/usr/local/git/bin/git push origin master
/usr/local/git/bin/git push remote_website master

;; For modification of local files
/usr/local/git/bin/git add .
/usr/local/git/bin/git commit -m "This is a modification . . . ."
/usr/local/git/bin/git push origin master
/usr/local/git/bin/git push remote_website master
like image 894
lawlist Avatar asked Apr 19 '14 23:04

lawlist


People also ask

Can git push to multiple remotes?

It is easy to synchronize code between multiple git repositories, especially, pushing to multiple remotes. This is helpful when you're maintaining mirrors / copies of the same repository. All you need to do is set up multiple push URLs on a remote and then perform git push to that remote as you usually do.

Can a repository have multiple remotes?

git/config to add remote and multiple remote URLs if you know the configuration format. Now you can push multiple remotes simultaneously by referring to the remote name with multiple remote URLs assigned. You can always push to multiple remote repositories without grouping them using formal bash syntax.

How do I push to a specific remote?

In order to push a Git branch to remote, you need to execute the “git push” command and specify the remote as well as the branch name to be pushed. If you are not already on the branch that you want to push, you can execute the “git checkout” command to switch to your branch.


1 Answers

EDIT (April 23, 2014):  Added a non-Magit solution to stage all, commit all (with a default commit message), and push to all remotes.

EDIT (April 24, 2014):  The printed output of all processes is now sent to the git-status-buffer, which is displayed at the end of the function -- with options for the user to choose what to do with the window -- e.g., delete window, delete buffer and window, or do nothing. Added some pretty coloring with (propertize "[...]" 'face 'font-lock-warning-face). The first draft of the function that relies upon a pre-existing installation of Magit has been moved to the bottom of this answer -- that function works, but is not as sophisticated as the current version that does not rely upon an installation of Magit.

(defvar git-status-buffer "*GIT-STATUS*"
  "The buffer name of the git-status-buffer.")

(defvar git-branch-name nil
"The current branch of the working Git directory.")
(make-variable-buffer-local 'git-branch-name)

(defvar git-remote-list nil
"List of remote locations -- e.g., lawlist_remote or github_remote.")
(make-variable-buffer-local 'git-remote-list)

(defvar git-commit-message (format "Committed -- %s" (current-time-string))
"The predetermined Git commit message.")
(make-variable-buffer-local 'git-commit-message)

(defun git-branch-process-filter (proc string)
  (with-current-buffer (get-buffer git-status-buffer)
    (set (make-local-variable 'git-branch-name)
      (car (split-string string "\n")))))

(defun git-push-process-filter (proc string)
  (when (string-match "password" string)
    (process-send-string
      proc
      (concat (read-passwd "Password:  ") "\n")))
  (when (and
      (not (string-equal "Password: " string))
      (not (string-equal "\n" string))
      (not (string-equal "stdin: is not a tty\n" string)))
    (with-current-buffer git-status-buffer
      (goto-char (point-max))
      (insert "\n" (replace-regexp-in-string "\^M" "\n" string)))))

(defun git-push-process-sentinel (proc string)
  (when (= 0 (process-exit-status proc))
    (with-current-buffer (get-buffer git-status-buffer)
      (insert
        "\n"
        (propertize
          (format "Process `%s` has finished pushing to `%s`." proc git-remote-name)
          'face 'font-lock-warning-face)
        "\n"))
    (throw 'exit nil)))

(defun stage-commit-push-all ()
"This function does the following:
  * Save the current working buffer if it has been modified.
  * Obtain the name of the selected branch in the current working buffer.
  * Gather a list of all remotes associated with working directory Git project.
  * Stage all -- `/usr/local/git/bin/git add .`
  * Commit all -- `/usr/local/git/bin/git commit -m [git-commit-message]`
  * Push to all remotes:  `/usr/local/git/bin/git push -v [remote] [current-branch]`"
(interactive)
  (when (buffer-modified-p)
    (save-buffer))
  (when (get-buffer git-status-buffer)
    (with-current-buffer (get-buffer git-status-buffer)
      (kill-local-variable 'git-remote-list)
      (kill-local-variable 'git-branch-name)
      (erase-buffer)))
  (start-process
    "current-branch"
    git-status-buffer
    "/usr/local/git/bin/git"
    "rev-parse"
    "--abbrev-ref"
    "HEAD")
  (set-process-filter (get-process "current-branch") 'git-branch-process-filter)
  (set-process-sentinel
    (get-process "current-branch")
    (lambda (p e) (when (= 0 (process-exit-status p))
      (set-process-sentinel
        (start-process
          "list-remotes"
          git-status-buffer
          "/usr/local/git/bin/git"
          "remote"
          "-v")
        (lambda (p e) (when (= 0 (process-exit-status p))
          (let* (
              beg
              end
              git-remote-name)
            (with-current-buffer (get-buffer git-status-buffer)
              (goto-char (point-max))
              (while (re-search-backward "\(push\)" nil t)
                (beginning-of-line 1)
                (setq beg (point))
                (re-search-forward "\t" nil t)
                (setq end (- (point) 1))
                (setq git-remote-name (buffer-substring-no-properties beg end))
                (setq git-remote-list
                  (append (cons git-remote-name git-remote-list)))) ))
          (set-process-sentinel
            (start-process
              "stage-all"
              git-status-buffer
              "/usr/local/git/bin/git"
              "add"
              ".")
            (lambda (p e) (when (= 0 (process-exit-status p))
              (with-current-buffer (get-buffer git-status-buffer)
                (goto-char (point-max))
                (insert "\n"))
              (set-process-sentinel
                (start-process
                  "commit-all"
                  git-status-buffer
                  "/usr/local/git/bin/git"
                  "commit"
                  "-m"
                  git-commit-message)
                (lambda (p e) (when (= 0 (process-exit-status p))
                  (mapcar (lambda (git-remote-name)
                    (let ((proc
                        (start-process
                          "push-process"
                          git-status-buffer
                          "/usr/local/git/bin/git"
                          "push"
                          "-v"
                          (format "%s" git-remote-name)
                          (format "%s"
                            (with-current-buffer (get-buffer git-status-buffer)
                              git-branch-name)) )))
                      (set-process-filter proc 'git-push-process-filter)
                      (set-process-sentinel proc 'git-push-process-sentinel)
                      (recursive-edit) ))
                    (with-current-buffer (get-buffer git-status-buffer)
                      git-remote-list) )
                  (display-buffer (get-buffer git-status-buffer))
                  (message (concat
                    git-status-buffer
                    " -- ["
                    (propertize "d" 'face 'font-lock-warning-face)
                    "]elete window | ["
                    (propertize "k" 'face 'font-lock-warning-face)
                    "]ill buffer + delete window | ["
                    (propertize "n" 'face 'font-lock-warning-face)
                    "]othing"))
                  (let* (
                      (git-window-options (read-char-exclusive))
                      (target-window (get-buffer-window git-status-buffer)))
                    (cond
                      ((eq git-window-options ?d)
                        (with-current-buffer (get-buffer git-status-buffer)
                          (delete-window target-window)))
                      ((eq git-window-options ?k)
                        (with-current-buffer (get-buffer git-status-buffer)
                          (delete-window target-window)
                          (kill-buffer (get-buffer git-status-buffer))))
                      ((eq git-window-options ?n)
                        (message "Done!"))
                      (t (message "You have exited the sub-function.")) ))
                )))))))))))))

FIRST DRAFT (April 19, 2014):  This function requires a pre-existing installation of Magit. The code set forth above does not require installation of Magit.

(defun push-to-all-remotes ()
"This function requires a pre-existing installation of Magit, and the function assumes
that the user has already staged and committed -- i.e., it only pushes to all remotes."
(interactive)
  (let* (beg end remote)
    (when (get-buffer "*REMOTES*")
      (with-current-buffer (get-buffer "*REMOTES*")
        (erase-buffer)))
    (set-process-sentinel
      (start-process
        "list-remotes"
        "*REMOTES*"
        "/usr/local/git/bin/git"
        "remote"
        "-v")
      (lambda (p e) (when (= 0 (process-exit-status p))
        (with-current-buffer (get-buffer "*REMOTES*")
          (goto-char (point-max))
          (while (re-search-backward "\(push\)" nil t)
            (beginning-of-line 1)
            (setq beg (point))
            (re-search-forward "\t" nil t)
            (setq end (- (point) 1))
            (setq remote (buffer-substring-no-properties beg end))
            (magit-run-git-async
              "push"
              "-v"
              remote
              (magit-get-current-branch))) ))))
    (display-buffer (get-buffer magit-process-buffer-name)) ))
like image 109
lawlist Avatar answered Oct 02 '22 08:10

lawlist