mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-04-24 23:31:33 -07:00
Improve vc-git-topic-outgoing-base fallback algorithm
* lisp/vc/vc-hg.el (vc-hg--bookmarks): New function. (vc-hg-trunk-or-topic-p): * lisp/vc/vc-git.el (vc-git--branch-remotes) (vc-git-trunk-or-topic-p): New optional BRANCH argument. (vc-trunk-or-topic-p): Declare. (vc-git-topic-outgoing-base): Consider only local trunks, if there are any (bug#80006). * lisp/vc/vc.el (trunk-or-topic-p): New optional BRANCH argument. (vc-trunk-or-topic-p): New function. (vc--outgoing-base): Call it.
This commit is contained in:
parent
cf27004e8c
commit
16f1ca1acc
3 changed files with 106 additions and 65 deletions
|
|
@ -772,8 +772,9 @@ or an empty string if none."
|
|||
(vc-git--out-match '("symbolic-ref" "HEAD")
|
||||
"^\\(refs/heads/\\)?\\(.+\\)$" 2))
|
||||
|
||||
(defun vc-git--branch-remotes ()
|
||||
"Return alist of configured remote branches for current branch.
|
||||
(defun vc-git--branch-remotes (&optional branch)
|
||||
"Return alist of configured remote branches for BRANCH.
|
||||
BRANCH defaults to the current branch.
|
||||
If there is a configured upstream, return the remote-tracking branch
|
||||
with key `upstream'. If there is a distinct configured push remote,
|
||||
return the remote-tracking branch there with key `push'.
|
||||
|
|
@ -783,7 +784,7 @@ ignored because that means we're not actually in a triangular workflow."
|
|||
;; an unwanted dependency on the setting of push.default.
|
||||
(cl-flet ((get (key)
|
||||
(string-trim-right (vc-git--out-str "config" key))))
|
||||
(let* ((branch (vc-git-working-branch))
|
||||
(let* ((branch (or branch (vc-git-working-branch)))
|
||||
(pull (get (format "branch.%s.remote" branch)))
|
||||
(merge (string-remove-prefix "refs/heads/"
|
||||
(get (format "branch.%s.merge"
|
||||
|
|
@ -799,12 +800,14 @@ ignored because that means we're not actually in a triangular workflow."
|
|||
alist
|
||||
(cl-acons 'push (format "%s/%s" push branch) alist)))))
|
||||
|
||||
(defun vc-git-trunk-or-topic-p ()
|
||||
(defun vc-git-trunk-or-topic-p (&optional branch)
|
||||
"Return `topic' if branch has distinct pull and push remotes, else nil.
|
||||
This is able to identify topic branches for certain forge workflows."
|
||||
(let ((remotes (vc-git--branch-remotes)))
|
||||
(let ((remotes (vc-git--branch-remotes branch)))
|
||||
(and (assq 'upstream remotes) (assq 'push remotes) 'topic)))
|
||||
|
||||
(declare-function vc-trunk-or-topic-p "vc")
|
||||
|
||||
(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
|
||||
|
|
@ -815,9 +818,10 @@ for outstanding 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 other 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
|
||||
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
|
||||
|
|
@ -832,6 +836,13 @@ them one-by-one, accepting the first that has an 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
|
||||
|
|
@ -839,7 +850,9 @@ them one-by-one, accepting the first that has an upstream.)"
|
|||
(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.
|
||||
;; 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 ".."))
|
||||
|
|
|
|||
|
|
@ -1962,9 +1962,23 @@ The return value is always a string."
|
|||
(let ((alist (vc-hg--working-branch)))
|
||||
(cdr (or (assq 'bookmark alist) (assq 'branch alist)))))
|
||||
|
||||
(defun vc-hg-trunk-or-topic-p ()
|
||||
"Return `topic' if there is a currently active bookmark, else nil."
|
||||
(and (assq 'bookmark (vc-hg--working-branch)) 'topic))
|
||||
(defun vc-hg--bookmarks ()
|
||||
"Return list of strings naming all bookmarks."
|
||||
(let (res)
|
||||
(with-temp-buffer
|
||||
(vc-hg-command t nil nil "bookmark" "--list")
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^ [ *] \\(\\S-+\\)" nil t)
|
||||
(push (match-string 1) res)))
|
||||
res))
|
||||
|
||||
(defun vc-hg-trunk-or-topic-p (&optional branch)
|
||||
"Return `topic' or nil for BRANCH or the currently active bookmark.
|
||||
If BRANCH names a bookmark, or BRANCH is nil but there is a currently
|
||||
active bookmark, return `topic'. Otherwise return nil."
|
||||
(if branch
|
||||
(member branch (vc-hg--bookmarks))
|
||||
(and (assq 'bookmark (vc-hg--working-branch)) 'topic)))
|
||||
|
||||
(defun vc-hg-topic-outgoing-base ()
|
||||
"Return outgoing base for current commit considered as a topic branch.
|
||||
|
|
|
|||
120
lisp/vc/vc.el
120
lisp/vc/vc.el
|
|
@ -615,17 +615,17 @@
|
|||
;;
|
||||
;; Return the name of the current branch, if there is one, else nil.
|
||||
;;
|
||||
;; - trunk-or-topic-p ()
|
||||
;; - trunk-or-topic-p (&optional branch)
|
||||
;;
|
||||
;; For the current branch, or the closest equivalent for a VCS without
|
||||
;; named branches, return `trunk' if it is definitely a longer-lived
|
||||
;; trunk branch, `topic' if it is definitely a shorter-lived topic
|
||||
;; branch, or nil if no general determination can be made.
|
||||
;; named branches, or the branch named BRANCH if non-nil, return
|
||||
;; `trunk' if it is definitely a longer-lived trunk branch, `topic' if
|
||||
;; it is definitely a shorter-lived topic branch, or nil if no general
|
||||
;; determination can be made.
|
||||
;;
|
||||
;; What counts as a longer-lived or shorter-lived branch for VC is
|
||||
;; explained in Info node `(emacs)Outstanding Changes' and in the
|
||||
;; docstrings for the `vc-trunk-branch-regexps' and
|
||||
;; `vc-topic-branch-regexps' user options.
|
||||
;; docstring for `vc-trunk-or-topic-p'.
|
||||
;;
|
||||
;; - topic-outgoing-base ()
|
||||
;;
|
||||
|
|
@ -3178,20 +3178,11 @@ There value can be of one of the following forms:
|
|||
whether a branch is a trunk. Emacs will ask the backend whether it
|
||||
thinks the current branch is a trunk.
|
||||
|
||||
In VC, trunk branches are those where you've finished sharing the work
|
||||
on the branch with your collaborators just as soon as you've checked it
|
||||
in, and in the case of a decentralized VCS, pushed it. In addition,
|
||||
typically you never delete trunk branches.
|
||||
|
||||
The specific VCS workflow you are using may only acknowledge a single
|
||||
trunk, and give other names to kinds of branches which VC would consider
|
||||
to be just further trunks.
|
||||
|
||||
If trunk branches in your project can be identified by name, include
|
||||
regexps matching their names in the value of this variable. This is
|
||||
more reliable than letting Emacs ask the backend.
|
||||
|
||||
See also `vc-topic-branch-regexps'."
|
||||
See also `vc-trunk-or-topic-p' and `vc-topic-branch-regexps'."
|
||||
:type '(choice (repeat :tag "Regexps" string)
|
||||
(cons :tag "Negated regexps"
|
||||
(const not) (repeat :tag "Regexps" string))
|
||||
|
|
@ -3221,21 +3212,11 @@ There value can be of one of the following forms:
|
|||
whether a branch is a topic branch. Emacs will ask the backend
|
||||
whether it thinks the current branch is a topic branch.
|
||||
|
||||
In VC, topic branches are those where checking in work, and pushing it
|
||||
in the case of a decentralized VCS, is not enough to complete the
|
||||
process of sharing the changes with your collaborators. In addition,
|
||||
it's required that you merge the topic branch into another branch.
|
||||
After this is done, typically you delete the topic branch.
|
||||
|
||||
Topic branches are sometimes called \"feature branches\", though it is
|
||||
also common for that term to be reserved for only a certain kind of
|
||||
topic branch.
|
||||
|
||||
If topic branches in your project can be identified by name, include
|
||||
regexps matching their names in the value of this variable. This is
|
||||
more reliable than letting Emacs ask the backend.
|
||||
|
||||
See also `vc-trunk-branch-regexps'."
|
||||
See also `vc-trunk-or-topic-p' and `vc-trunk-branch-regexps'."
|
||||
:type '(choice (repeat :tag "Regexps" string)
|
||||
(cons :tag "Negated regexps"
|
||||
(const not) (repeat :tag "Regexps" string))
|
||||
|
|
@ -3287,6 +3268,51 @@ defcustoms don't decide the matter of which kind of branch this is."
|
|||
(trunk 'trunk)
|
||||
(topic 'topic)))))
|
||||
|
||||
(defun vc-trunk-or-topic-p (&optional branch backend)
|
||||
"Return whether BRANCH, or the current branch, is a trunk or a topic.
|
||||
Returns `trunk' if BRANCH is definitely a trunk, `topic' if BRANCH is
|
||||
definitely a topic, or nil if no general determination can be made.
|
||||
|
||||
In VC, trunk branches are those where you've finished sharing the work
|
||||
on the branch with your collaborators just as soon as you've checked it
|
||||
in, and in the case of a decentralized VCS, pushed it. In addition,
|
||||
typically you never delete trunk branches. The specific VCS workflow
|
||||
you are using may only acknowledge a single trunk, and give other names
|
||||
to kinds of branches which VC would consider to be just further trunks.
|
||||
|
||||
Topic branches are those where checking in work, and pushing it in the
|
||||
case of a decentralized VCS, is not enough to complete the process of
|
||||
sharing the changes with your collaborators. In addition, it's required
|
||||
that you merge the topic branch into another branch. After this is
|
||||
done, typically you delete the topic branch. Topic branches are
|
||||
sometimes called \"feature branches\", though it is also common for that
|
||||
term to be reserved for only a certain kind of topic branch.
|
||||
|
||||
Determining whether BRANCH is a trunk or a topic proceeds in two stages:
|
||||
1. If BRANCH is non-nil, compare that name against
|
||||
`vc-trunk-branch-regexps' and `vc-topic-branch-regexps', which see.
|
||||
If BRANCH is nil, ask the backend for the name of the current branch.
|
||||
If the backend returns a name, compare it against
|
||||
`vc-trunk-branch-regexps' and `vc-topic-branch-regexps'.
|
||||
2. If that doesn't settle it, either because the backend reports that
|
||||
the current branch has no name or because comparing the name against
|
||||
the two regexp defcustoms yields no decisive answer, call the backend's
|
||||
`trunk-or-topic-p' VC API function.
|
||||
|
||||
If trunk and/or topic branches in your project can be identified by
|
||||
name, include regexps matching those names in `vc-trunk-branch-regexps'
|
||||
and/or `vc-topic-branch-regexps' as appropriate. This is more reliable
|
||||
than letting Emacs ask the backend.
|
||||
|
||||
BACKEND is the VC backend."
|
||||
(let* ((backend (or backend (vc-deduce-backend)))
|
||||
(branch (or branch (vc-call-backend backend 'working-branch))))
|
||||
(or (and branch (vc--match-branch-name-regexps branch))
|
||||
;; It's okay to pass nil BRANCH here, which is what happens in
|
||||
;; case of a VCS without named branches or where the current
|
||||
;; branch has no name.
|
||||
(vc-call-backend backend 'trunk-or-topic-p branch))))
|
||||
|
||||
(defun vc--outgoing-base (backend)
|
||||
"Return an outgoing base for the current branch under VC backend BACKEND.
|
||||
The outgoing base is the upstream location for which outstanding changes
|
||||
|
|
@ -3294,30 +3320,18 @@ on this branch are destined once they are no longer outstanding.
|
|||
|
||||
There are two stages to determining the outgoing base.
|
||||
First we decide whether we think this is a shorter-lived or a
|
||||
longer-lived (\"trunk\") branch (see `vc-trunk-branch-regexps' and
|
||||
`vc-topic-branch-regexps' regarding this distinction), as follows:
|
||||
1. Ask the backend for the name of the current branch.
|
||||
If it returns non-nil, compare that name against
|
||||
`vc-trunk-branch-regexps' and `vc-topic-branch-regexps'.
|
||||
2. If that doesn't settle it, either because the backend returns nil for
|
||||
the name of the current branch, or because comparing the name against
|
||||
the two regexp defcustoms yields no decisive answer, call BACKEND's
|
||||
`trunk-or-topic-p' VC API function.
|
||||
3. If that doesn't settle it either, assume this is a shorter-lived
|
||||
branch. This is based on how it's commands primarily intended for
|
||||
working with shorter-lived branches that call this function.
|
||||
longer-lived (\"trunk\") branch by calling `vc-trunk-or-topic-p'.
|
||||
If that function returns nil, assume this is a shorter-lived branch.
|
||||
This is based on how it's commands primarily intended for working with
|
||||
shorter-lived branches that call this function.
|
||||
Second, if we have determined that this is a trunk, return nil, meaning
|
||||
that the outgoing base is the place to which `vc-push' would push.
|
||||
Otherwise, we have determined that this is a shorter-lived branch, and
|
||||
we return the value of calling BACKEND's `topic-outgoing-base' VC API
|
||||
function."
|
||||
;; For further discussion see bug#80006.
|
||||
(let* ((branch (vc-call-backend backend 'working-branch))
|
||||
(type (or (and branch (vc--match-branch-name-regexps branch))
|
||||
(vc-call-backend backend 'trunk-or-topic-p)
|
||||
'topic)))
|
||||
(and (eq type 'topic)
|
||||
(vc-call-backend backend 'topic-outgoing-base))))
|
||||
(and (memq (vc-trunk-or-topic-p nil backend) '(topic nil))
|
||||
(vc-call-backend backend 'topic-outgoing-base)))
|
||||
|
||||
(defun vc--outgoing-base-mergebase (backend &optional upstream-location refresh)
|
||||
"Return, under VC backend BACKEND, the merge base with UPSTREAM-LOCATION.
|
||||
|
|
@ -3350,8 +3364,8 @@ For a trunk branch this is always the place \\[vc-push] would push to.
|
|||
For a topic branch, see whether the branch matches one of
|
||||
`vc-trunk-branch-regexps' or `vc-topic-branch-regexps', or else query
|
||||
the backend for an appropriate outgoing base.
|
||||
See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding
|
||||
the difference between trunk and topic branches.
|
||||
See `vc-trunk-or-topic-p' regarding the difference between trunk and
|
||||
topic branches.
|
||||
|
||||
When called interactively with a prefix argument, prompt for
|
||||
UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION
|
||||
|
|
@ -3380,8 +3394,8 @@ For a trunk branch this is always the place \\[vc-push] would push to.
|
|||
For a topic branch, see whether the branch matches one of
|
||||
`vc-trunk-branch-regexps' or `vc-topic-branch-regexps', or else query
|
||||
the backend for an appropriate outgoing base.
|
||||
See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding
|
||||
the difference between trunk and topic branches.
|
||||
See `vc-trunk-or-topic-p' regarding the difference between trunk and
|
||||
topic branches.
|
||||
|
||||
When called interactively with a prefix argument, prompt for
|
||||
UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION
|
||||
|
|
@ -3416,8 +3430,8 @@ For a trunk branch this is always the place \\[vc-push] would push to.
|
|||
For a topic branch, see whether the branch matches one of
|
||||
`vc-trunk-branch-regexps' or `vc-topic-branch-regexps', or else query
|
||||
the backend for an appropriate outgoing base.
|
||||
See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding
|
||||
the difference between trunk and topic branches.
|
||||
See `vc-trunk-or-topic-p' regarding the difference between trunk and
|
||||
topic branches.
|
||||
|
||||
When called interactively with a prefix argument, prompt for
|
||||
UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION
|
||||
|
|
@ -3450,8 +3464,8 @@ For a trunk branch this is always the place \\[vc-push] would push to.
|
|||
For a topic branch, see whether the branch matches one of
|
||||
`vc-trunk-branch-regexps' or `vc-topic-branch-regexps', or else query
|
||||
the backend for an appropriate outgoing base.
|
||||
See `vc-trunk-branch-regexps' and `vc-topic-branch-regexps' regarding
|
||||
the difference between trunk and topic branches.
|
||||
See `vc-trunk-or-topic-p' regarding the difference between trunk and
|
||||
topic branches.
|
||||
|
||||
When called interactively with a prefix argument, prompt for
|
||||
UPSTREAM-LOCATION. In some version control systems, UPSTREAM-LOCATION
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue