Function: vc-git-topic-outgoing-base
vc-git-topic-outgoing-base is a byte-compiled function defined in
vc-git.el.gz.
Signature
(vc-git-topic-outgoing-base)
Documentation
Return the outgoing base for the current branch as a string.
This works by considering the current branch as a topic branch
(whether or not it actually is).
If there is a distinct push remote for this branch, assume the target for unintegrated changes is the tracking branch, and return that.
Otherwise, fall back to the following algorithm, which requires that the
corresponding trunk exists as a local branch. Find all merge bases
between the current branch and either all local trunks, if there are any
positively identified trunks, or just all local branches. Each of these
is a commit on the current branch. Use git merge-base --independent
on them all to find the topologically most recent. Take the branch for
which that commit is a merge base with the current branch to be the
branch into which the current branch will eventually be merged. Find
its upstream. (If there is more than one branch whose merge base with
the current branch is that same topologically most recent commit, try
them one-by-one, accepting the first that has an upstream.)
Source Code
;; Defined in /usr/src/emacs/lisp/vc/vc-git.el.gz
(defun vc-git-topic-outgoing-base ()
"Return the outgoing base for the current branch as a string.
This works by considering the current branch as a topic branch
(whether or not it actually is).
If there is a distinct push remote for this branch, assume the target
for unintegrated changes is the tracking branch, and return that.
Otherwise, fall back to the following algorithm, which requires that the
corresponding trunk exists as a local branch. Find all merge bases
between the current branch and either all local trunks, if there are any
positively identified trunks, or just all local branches. Each of these
is a commit on the current branch. Use `git merge-base --independent'
on them all to find the topologically most recent. Take the branch for
which that commit is a merge base with the current branch to be the
branch into which the current branch will eventually be merged. Find
its upstream. (If there is more than one branch whose merge base with
the current branch is that same topologically most recent commit, try
them one-by-one, accepting the first that has an upstream.)"
(cond*
;; Easy case.
((bind-and* (remotes (vc-git--branch-remotes))
(_ (assq 'push remotes))
(upstream (assq 'upstream remotes)))
(cdr upstream))
;; Fallback algorithm.
((bind* (branches (vc-git-branches))
(current (car branches))
;; If there are any branches we can positively identify as
;; trunks, consider only those.
(trunks (cl-remove-if-not (lambda (b)
(eq (vc-trunk-or-topic-p b 'Git)
'trunk))
branches))
(branches (or trunks branches))
raw-merge-bases)
(with-temp-buffer
(cl-labels
((get-line ()
(buffer-substring (point) (pos-eol)))
(git-merge-base (commit1 commit2)
;; Return all merge bases between COMMIT1 and COMMIT2.
;; Memoized to reduce shelling out to Git
;; (that doesn't actually help at present, but may be
;; useful in the future).
(let* ((sorted (sort (list commit1 commit2)))
;; Git branch names may not contain "..".
(key (string-join sorted ".."))
(val (assoc key raw-merge-bases)))
(if (consp val)
(cdr val)
(erase-buffer)
(and (apply #'vc-git--out-ok "merge-base" "--all" sorted)
(let (res)
(goto-char (point-min))
(while (not (eobp))
(push (get-line) res)
(forward-line 1))
(setf (alist-get key raw-merge-bases nil nil #'equal)
(sort res)))))))
(branch-merge-bases (branch)
;; Return alist mapping merge bases with BRANCH to the
;; names of the relevant local branches.
(let (merge-bases)
(dolist (other branches)
(unless (equal other branch)
(dolist (merge-base (git-merge-base other branch))
(push other (alist-get merge-base merge-bases
nil nil #'equal)))))
merge-bases))
(independent-merge-base (commits)
;; Do 'git merge-base --independent' on COMMITS.
;; If this command prints more than one result we
;; currently just pick the first.
(erase-buffer)
(unless (apply #'vc-git--out-ok "merge-base" "--independent"
commits)
(error "`git merge-base --independent' failed"))
(goto-char (point-min))
(get-line))
(branch-upstream (branch)
;; Return upstream for BRANCH if it has one.
(erase-buffer)
(and (vc-git--out-ok "for-each-ref" "--format=%(upstream:short)"
(format "refs/heads/%s" branch))
(goto-char (point-min))
(let ((res (get-line)))
(and (not (string-empty-p res)) res)))))
(let* ((merge-bases (branch-merge-bases current))
;; Topologically most recent merge base between CURRENT
;; and another local branch or branches.
(indep (independent-merge-base (mapcar #'car merge-bases)))
;; Local branches with which INDEP is a merge-base with
;; CURRENT.
(indep-branches (cdr (assoc indep merge-bases))))
(catch 'ret
(dolist (target indep-branches)
(when-let* ((upstream (branch-upstream target)))
(throw 'ret upstream))))))))))