aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/vc/vc-git.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/vc/vc-git.el')
-rw-r--r--lisp/vc/vc-git.el143
1 files changed, 84 insertions, 59 deletions
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 73db9c0f181..5e51b28fb37 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -772,70 +772,91 @@ or an empty string if none."
772 (vc-git--out-match '("symbolic-ref" "HEAD") 772 (vc-git--out-match '("symbolic-ref" "HEAD")
773 "^\\(refs/heads/\\)?\\(.+\\)$" 2)) 773 "^\\(refs/heads/\\)?\\(.+\\)$" 2))
774 774
775(defun vc-git--branch-remotes ()
776 "Return alist of configured remote branches for current branch.
777If there is a configured upstream, return the remote-tracking branch
778with key `upstream'. If there is a distinct configured push remote,
779return the remote-tracking branch there with key `push'.
780A configured push remote that's just the same as the upstream remote is
781ignored because that means we're not actually in a triangular workflow."
782 ;; Possibly we could simplify this using @{push}, but that may involve
783 ;; an unwanted dependency on the setting of push.default.
784 (cl-flet ((get (key)
785 (string-trim-right (vc-git--out-str "config" key))))
786 (let* ((branch (vc-git-working-branch))
787 (pull (get (format "branch.%s.remote" branch)))
788 (merge (string-remove-prefix "refs/heads/"
789 (get (format "branch.%s.merge"
790 branch))))
791 (push (get (format "branch.%s.pushRemote" branch)))
792 (push (if (string-empty-p push)
793 (get "remote.pushDefault")
794 push))
795 (alist (and (not (string-empty-p pull))
796 (not (string-empty-p merge))
797 `((upstream . ,(format "%s/%s" pull merge))))))
798 (if (or (string-empty-p push) (equal push pull))
799 alist
800 (cl-acons 'push (format "%s/%s" push branch) alist)))))
801
775(defun vc-git-trunk-or-topic-p () 802(defun vc-git-trunk-or-topic-p ()
776 "Return `topic' if branch has distinct pull and push remotes, else nil. 803 "Return `topic' if branch has distinct pull and push remotes, else nil.
777This is able to identify topic branches for certain forge workflows." 804This is able to identify topic branches for certain forge workflows."
778 (let* ((branch (vc-git-working-branch)) 805 (let ((remotes (vc-git--branch-remotes)))
779 (merge (string-trim-right 806 (and (assq 'upstream remotes) (assq 'push remotes) 'topic)))
780 (vc-git--out-str "config" (format "branch.%s.remote"
781 branch))))
782 (push (string-trim-right
783 (vc-git--out-str "config" (format "branch.%s.pushRemote"
784 branch))))
785 (push (if (string-empty-p push)
786 (string-trim-right
787 (vc-git--out-str "config" "remote.pushDefault"))
788 push)))
789 (and (plusp (length merge))
790 (plusp (length push))
791 (not (equal merge push))
792 'topic)))
793 807
794(defun vc-git-topic-outgoing-base () 808(defun vc-git-topic-outgoing-base ()
795 "Return the outgoing base for the current branch as a string. 809 "Return the outgoing base for the current branch as a string.
796This works by considering the current branch as a topic branch 810This works by considering the current branch as a topic branch
797(whether or not it actually is). 811(whether or not it actually is).
798Requires that the corresponding trunk exists as a local branch. 812
799 813If there is a distinct push remote for this branch, assume the target
800The algorithm employed is as follows. Find all merge bases between the 814for outstanding changes is the tracking branch, and return that.
801current branch and other local branches. Each of these is a commit on 815
802the current branch. Use `git merge-base --independent' on them all to 816Otherwise, fall back to the following algorithm, which requires that the
803find the topologically most recent. Take the branch for which that 817corresponding trunk exists as a local branch. Find all merge bases
804commit is a merge base with the current branch to be the branch into 818between the current branch and other local branches. Each of these is a
805which the current branch will eventually be merged. Find its upstream. 819commit on the current branch. Use `git merge-base --independent' on
806(If there is more than one branch whose merge base with the current 820them all to find the topologically most recent. Take the branch for
807branch is that same topologically most recent commit, try them 821which that commit is a merge base with the current branch to be the
808one-by-one, accepting the first that has an upstream.)" 822branch into which the current branch will eventually be merged. Find
809 (cl-flet ((get-line () (buffer-substring (point) (pos-eol)))) 823its upstream. (If there is more than one branch whose merge base with
810 (let* ((branches (vc-git-branches)) 824the current branch is that same topologically most recent commit, try
811 (current (pop branches)) 825them one-by-one, accepting the first that has an upstream.)"
812 merge-bases) 826 (if-let* ((remotes (vc-git--branch-remotes))
813 (with-temp-buffer 827 (_ (assq 'push remotes))
814 (dolist (branch branches) 828 (upstream (assq 'upstream remotes)))
815 (erase-buffer) 829 (cdr upstream)
816 (when (vc-git--out-ok "merge-base" "--all" branch current) 830 (cl-flet ((get-line () (buffer-substring (point) (pos-eol))))
817 (goto-char (point-min)) 831 (let* ((branches (vc-git-branches))
818 (while (not (eobp)) 832 (current (pop branches))
819 (push branch 833 merge-bases)
820 (alist-get (get-line) merge-bases nil nil #'equal)) 834 (with-temp-buffer
821 (forward-line 1)))) 835 (dolist (branch branches)
822 (erase-buffer)
823 (unless (apply #'vc-git--out-ok "merge-base" "--independent"
824 (mapcar #'car merge-bases))
825 (error "`git merge-base --independent' failed"))
826 ;; If 'git merge-base --independent' printed more than one line,
827 ;; just pick the first.
828 (goto-char (point-min))
829 (catch 'ret
830 (dolist (target (cdr (assoc (get-line) merge-bases)))
831 (erase-buffer) 836 (erase-buffer)
832 (when (vc-git--out-ok "for-each-ref" 837 (when (vc-git--out-ok "merge-base" "--all" branch current)
833 "--format=%(upstream:short)"
834 (concat "refs/heads/" target))
835 (goto-char (point-min)) 838 (goto-char (point-min))
836 (let ((outgoing-base (get-line))) 839 (while (not (eobp))
837 (unless (string-empty-p outgoing-base) 840 (push branch (alist-get (get-line) merge-bases
838 (throw 'ret outgoing-base)))))))))) 841 nil nil #'equal))
842 (forward-line 1))))
843 (erase-buffer)
844 (unless (apply #'vc-git--out-ok "merge-base" "--independent"
845 (mapcar #'car merge-bases))
846 (error "`git merge-base --independent' failed"))
847 ;; If 'git merge-base --independent' printed more than one
848 ;; line, just pick the first.
849 (goto-char (point-min))
850 (catch 'ret
851 (dolist (target (cdr (assoc (get-line) merge-bases)))
852 (erase-buffer)
853 (when (vc-git--out-ok "for-each-ref"
854 "--format=%(upstream:short)"
855 (concat "refs/heads/" target))
856 (goto-char (point-min))
857 (let ((outgoing-base (get-line)))
858 (unless (string-empty-p outgoing-base)
859 (throw 'ret outgoing-base)))))))))))
839 860
840(defun vc-git-dir--branch-headers () 861(defun vc-git-dir--branch-headers ()
841 "Return headers for branch-related information." 862 "Return headers for branch-related information."
@@ -1451,7 +1472,9 @@ line of the commit message in an entry with key \"Subject\"."
1451 (if (eq system-type 'windows-nt) 1472 (if (eq system-type 'windows-nt)
1452 locale-coding-system 1473 locale-coding-system
1453 coding-system-for-write))) 1474 coding-system-for-write)))
1454 (vc-git--call input-file t "mailinfo" msg-file patch-file)) 1475 (vc-git--call input-file t "mailinfo"
1476 (file-local-name msg-file)
1477 (file-local-name patch-file)))
1455 (goto-char (point-min)) 1478 (goto-char (point-min))
1456 ;; git-mailinfo joins up any header continuation lines for us. 1479 ;; git-mailinfo joins up any header continuation lines for us.
1457 (while (re-search-forward "^\\([^\t\n\s:]+\\):\\(.*\\)$" nil t) 1480 (while (re-search-forward "^\\([^\t\n\s:]+\\):\\(.*\\)$" nil t)
@@ -1591,7 +1614,9 @@ If PROMPT is non-nil, prompt for the Git command to run."
1591 (vc-filter-command-function 1614 (vc-filter-command-function
1592 (if prompt 1615 (if prompt
1593 (lambda (&rest args) 1616 (lambda (&rest args)
1594 (cl-destructuring-bind (&whole args git _ flags) 1617 (cl-destructuring-bind
1618 (&whole args git _ flags
1619 &aux (vc-user-edit-command-history 'vc-git-history))
1595 (apply #'vc-user-edit-command args) 1620 (apply #'vc-user-edit-command args)
1596 (setq git-program git 1621 (setq git-program git
1597 command (car flags) 1622 command (car flags)
@@ -2567,9 +2592,9 @@ This command shares argument histories with \\[rgrep] and \\[grep]."
2567 ;; In *vc-dir*, if nothing is marked, act on the whole working tree 2592 ;; In *vc-dir*, if nothing is marked, act on the whole working tree
2568 ;; regardless of the position of point. This preserves historical 2593 ;; regardless of the position of point. This preserves historical
2569 ;; behavior and is also probably more useful. 2594 ;; behavior and is also probably more useful.
2570 (if (derived-mode-p 'vc-dir-mode) 2595 (mapcar #'file-relative-name (if (derived-mode-p 'vc-dir-mode)
2571 (vc-dir-marked-files) 2596 (vc-dir-marked-files)
2572 (cadr (vc-deduce-fileset)))) 2597 (cadr (vc-deduce-fileset)))))
2573 2598
2574(defun vc-git-stash (name) 2599(defun vc-git-stash (name)
2575 "Create a stash named NAME. 2600 "Create a stash named NAME.