Emacs -- How to push a Git repository to multiple remotes
Asked Answered
S

1

6

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://mcmap.net/q/13089/-how-can-i-pull-push-from-multiple-remote-locations) , 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
Septivalent answered 19/4, 2014 at 23:59 Comment(0)
S
4

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)) ))
Septivalent answered 20/4, 2014 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.