Merge lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/bzr/move-interbranch-fetch2
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/move-interbranch-fetch1
Diff against target: 380 lines (+55/-85)
10 files modified
bzrlib/branch.py (+26/-49)
bzrlib/fetch.py (+3/-10)
bzrlib/merge.py (+1/-8)
bzrlib/remote.py (+10/-9)
bzrlib/smart/branch.py (+2/-2)
bzrlib/tests/per_branch/test_branch.py (+8/-0)
bzrlib/tests/test_remote.py (+1/-1)
bzrlib/tests/test_revisionspec.py (+2/-4)
bzrlib/workingtree_4.py (+1/-1)
doc/en/release-notes/bzr-2.4.txt (+1/-1)
To merge this branch: bzr merge lp:~jelmer/bzr/move-interbranch-fetch2
Reviewer Review Type Date Requested Status
Andrew Bennetts Needs Fixing
bzr-core Pending
Review via email: mp+54958@code.launchpad.net

This proposal has been superseded by a proposal from 2011-04-20.

Description of the change

Make Branch.fetch() take a fetch_tags argument rather than fetch_spec. This makes it possible for Merger._maybe_fetch() to fetch the branch revisions without specifying a fetch_spec (which doesn't work with remote foreign repositories that don't allow direct graph access).

Since the fetch_spec argument was added in 2.4, I don't think there is a need to deprecate it first.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew-
Can you look at this one? I'm not sure that I prefer this method. I
think it is fine to change how we search for items, but I felt that
FetchSpec was the more generic interface.
John
=:->

- -------- Original Message --------
Subject: [Merge] lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr
Date: Sat, 26 Mar 2011 15:18:05 -0000
From: Jelmer Vernooij <email address hidden>
Reply-To: <email address hidden>
To: <email address hidden>, Andrew Bennetts
<email address hidden>

Jelmer Vernooij has proposed merging
lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr with
lp:~jelmer/bzr/move-interbranch-fetch1 as a prerequisite.

Requested reviews:
  bzr-core (bzr-core)
  Andrew Bennetts (spiv): code
Related bugs:
  Bug #741760 in Bazaar: ""bzr merge" always looks at revision graph"
  https://bugs.launchpad.net/bzr/+bug/741760

For more details, see:
https://code.launchpad.net/~jelmer/bzr/move-interbranch-fetch2/+merge/54958

Make Branch.fetch() take a fetch_tags argument rather than fetch_spec.
This makes it possible for Merger._maybe_fetch() to fetch the branch
revisions without specifying a fetch_spec (which doesn't work with
remote foreign repositories that don't allow direct graph access).

Since the fetch_spec argument was added in 2.4, I don't think there is a
need to deprecate it first.
- --
https://code.launchpad.net/~jelmer/bzr/move-interbranch-fetch2/+merge/54958
Your team bzr-core is requested to review the proposed merge of
lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk2YkekACgkQJdeBCYSNAAPDPwCdGs5DDx1uCCMyP5UcOGdbdE7W
wTkAoJukPhEgwObgCNBt3tP5S5vGYIeV
=rRiH
-----END PGP SIGNATURE-----

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

On Sun, 2011-04-03 at 15:31 +0000, John A Meinel wrote:
> Andrew-
> Can you look at this one? I'm not sure that I prefer this method. I
> think it is fine to change how we search for items, but I felt that
> FetchSpec was the more generic interface.
FWIW The generic interface is still usable, it just requires you to call
fetch on the repository rather than on the branch.

Cheers,

Jelmer

> - -------- Original Message --------
> Subject: [Merge] lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr
> Date: Sat, 26 Mar 2011 15:18:05 -0000
> From: Jelmer Vernooij <email address hidden>
> Reply-To: <email address hidden>
> To: <email address hidden>, Andrew Bennetts
> <email address hidden>
>
> Jelmer Vernooij has proposed merging
> lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr with
> lp:~jelmer/bzr/move-interbranch-fetch1 as a prerequisite.
>
> Requested reviews:
> bzr-core (bzr-core)
> Andrew Bennetts (spiv): code
> Related bugs:
> Bug #741760 in Bazaar: ""bzr merge" always looks at revision graph"
> https://bugs.launchpad.net/bzr/+bug/741760
>
> For more details, see:
> https://code.launchpad.net/~jelmer/bzr/move-interbranch-fetch2/+merge/54958
>
> Make Branch.fetch() take a fetch_tags argument rather than fetch_spec.
> This makes it possible for Merger._maybe_fetch() to fetch the branch
> revisions without specifying a fetch_spec (which doesn't work with
> remote foreign repositories that don't allow direct graph access).
>
> Since the fetch_spec argument was added in 2.4, I don't think there is a
> need to deprecate it first.
> - --
> https://code.launchpad.net/~jelmer/bzr/move-interbranch-fetch2/+merge/54958
> Your team bzr-core is requested to review the proposed merge of
> lp:~jelmer/bzr/move-interbranch-fetch2 into lp:bzr.
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (Cygwin)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
>
> iEYEARECAAYFAk2YkekACgkQJdeBCYSNAAPDPwCdGs5DDx1uCCMyP5UcOGdbdE7W
> wTkAoJukPhEgwObgCNBt3tP5S5vGYIeV
> =rRiH
> -----END PGP SIGNATURE-----
>

Revision history for this message
Andrew Bennetts (spiv) wrote :

Special-casing tags over other revisions that may be related to a branch (like loom threads) feels weird to me.

So looking at this I think my initial impression on IRC is right: the core problem is that the purpose of Branch.fetch/InterBranch.fetch isn't clear.

Most of bzrlib itself uses Repository.fetch. Branch.fetch in pure bzrlib (without your patch) is basically a convenience method for “do a repo fetch of the source branch's tip”. That raises two obvious questions for me: 1) why bother accepting stop_revision or fetch_spec at all (if you need an unusual fetch, just use the repo), and 2) why doesn't it fetch tags if its purpose is to be a convenient and sensible default fetch for branches.

[For 2) the immediate answer is that during development of my fetch-all-tags fixes that Branch.fetch probably never showed up as relevant to making tagged revisions be fetched during 'bzr branch/pull/update/merge', which is a pretty strong indication it is not used much.]

So given that, I'd be tempted to delete Branch.fetch entirely! But I'm guessing it may be useful for foreign branch support? I can see at a glance that e.g. bzr-svn implements its own InterBranch.fetch etc, but I can't tell from a glance if that's functionality that can be easily implemented elsewhere (e.g. heads_to_fetch) or not. What do you think?

If deleting Branch.fetch isn't an (easy) option, what about going the other direction to this patch, and removing its arguments entirely? So in bzrlib it would become:

"""
class GenericInterBranch(InterBranch):
    # …
    def fetch(self):
        must_have_revs, optional_revs = self.source.heads_to_fetch()
        return self.target.repository.fetch(fetch_spec=NotInOtherForHeads(self.target.repository, self.source.repository, must_have_revs, optional_revs)
"""

What do you think?

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

> Special-casing tags over other revisions that may be related to a branch (like
> loom threads) feels weird to me.
>
> So looking at this I think my initial impression on IRC is right: the core
> problem is that the purpose of Branch.fetch/InterBranch.fetch isn't clear.
>
> Most of bzrlib itself uses Repository.fetch. Branch.fetch in pure bzrlib
> (without your patch) is basically a convenience method for “do a repo fetch of
> the source branch's tip”. That raises two obvious questions for me: 1) why
> bother accepting stop_revision or fetch_spec at all (if you need an unusual
> fetch, just use the repo), and 2) why doesn't it fetch tags if its purpose is
> to be a convenient and sensible default fetch for branches.
>
> [For 2) the immediate answer is that during development of my fetch-all-tags
> fixes that Branch.fetch probably never showed up as relevant to making tagged
> revisions be fetched during 'bzr branch/pull/update/merge', which is a pretty
> strong indication it is not used much.]
>
> So given that, I'd be tempted to delete Branch.fetch entirely! But I'm
> guessing it may be useful for foreign branch support? I can see at a glance
> that e.g. bzr-svn implements its own InterBranch.fetch etc, but I can't tell
> from a glance if that's functionality that can be easily implemented elsewhere
> (e.g. heads_to_fetch) or not. What do you think?
Branch.fetch() is very useful for foreign branches, as it makes it possible to determine the tags to fetch and fetching them in one go. heads_to_fetch() requires opening a connection to discover what tags to fetch, then later opening a new connection when actually fetching them.

> If deleting Branch.fetch isn't an (easy) option, what about going the other
> direction to this patch, and removing its arguments entirely? So in bzrlib it
> would become:
>
> """
> class GenericInterBranch(InterBranch):
> # …
> def fetch(self):
> must_have_revs, optional_revs = self.source.heads_to_fetch()
> return self.target.repository.fetch(fetch_spec=NotInOtherForHeads(self
> .target.repository, self.source.repository, must_have_revs, optional_revs)
> """
>
> What do you think?
Removing fetch_tags might make sense (it just seems to be set to true everywhere). Removing stop_revision would make it impossible to use Branch.fetch from e.g. Merger._maybe_fetch(), which specifies a revision id.

Your proposed GenericInterBranch.fetch() implementation seems reasonable, though perhaps we could add a stop_revision there (and in heads_to_fetch()) too?

Cheers,

Jelmer

Revision history for this message
Andrew Bennetts (spiv) wrote :

Jelmer Vernooij wrote:
[…]
> > So given that, I'd be tempted to delete Branch.fetch entirely! But I'm
> > guessing it may be useful for foreign branch support? I can see at a glance
> > that e.g. bzr-svn implements its own InterBranch.fetch etc, but I can't tell
> > from a glance if that's functionality that can be easily implemented elsewhere
> > (e.g. heads_to_fetch) or not. What do you think?
> Branch.fetch() is very useful for foreign branches, as it makes it possible to
> determine the tags to fetch and fetching them in one go. heads_to_fetch()
> requires opening a connection to discover what tags to fetch, then later
> opening a new connection when actually fetching them.

Ah. That's a shame. Ok I guess we keep it then.

[…]
> Removing fetch_tags might make sense (it just seems to be set to true
> everywhere). Removing stop_revision would make it impossible to use
> Branch.fetch from e.g. Merger._maybe_fetch(), which specifies a revision id.
>
> Your proposed GenericInterBranch.fetch() implementation seems reasonable,
> though perhaps we could add a stop_revision there (and in heads_to_fetch())
> too?

Ok, that sounds like a good plan to me.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Updated to pass the stop_revision and include_tags on to Branch.heads_to_fetch.

I had to leave include_tags in as InterBranch.update_revisions() has a fetch_tags argument.

Does this seem more reasonable?

Revision history for this message
Andrew Bennetts (spiv) wrote :

I'm confused: this patch still has fetch_tags on Branch.fetch and InterBranch.fetch. Wasn't the revised plan that these would only take stop_revision and no other args?

I'm not very comfortable with heads_to_fetch taking include_tags, although it's less troubling than having it on fetch. I'm ok with it as an intermediate step to making things work if that's what's needed, but I don't think it's the long term answer.

It makes me wonder if heads_to_fetch should actually take no args but return something more structured, with room for optional extra fields, e.g.: something like a two-tuple of (heads_dict, type_advice), which looks like:

  heads_dict: {'tip': [revid], 'tags': [revid, revid, …]}
  type_advice: {'tip': ['must fetch'], 'tags': ['optional', 'ghost ok']}

(Probably an actual formal object would be better, but you get the idea)

Foreign implementations could add new things into the data returned, e.g. 'loom threads', and advise whether they are must-fetch or not, etc. This would also resolve the XXX about fetching a different stop_revision than tip without potentially removing an important revid (if it's the tip and a loom thread, then replacing the 'tip' value won't remove it from the heads-to-fetch info entirely).

Your change to the heads_to_fetch HPSS code isn't sufficiently tested. I don't think we allow None as an RPC argument. And there should at least be tests on the client and server what happens when a stop_revision is and isn't specified (and ditto include_tags)

review: Needs Fixing
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi spiv,

On Wed, 2011-04-06 at 05:51 +0000, Andrew Bennetts wrote:
> Review: Needs Fixing

> I'm confused: this patch still has fetch_tags on
> Branch.fetch and InterBranch.fetch. Wasn't the revised plan that these
> would only take stop_revision and no other args?
See my comment earlier: Branch.update_revisions takes a fetch_tags
argument, hence we still needed fetch_tags/include_tags as an argument.
Alternatively, we could consider dropping that argument to
update_revisions too, I'm not sure where it's used.

> I'm not very comfortable with heads_to_fetch taking include_tags,
> although it's less troubling than having it on fetch. I'm ok with it
> as an intermediate step to making things work if that's what's needed,
> but I don't think it's the long term answer.
>
> It makes me wonder if heads_to_fetch should actually take no args but
> return something more structured, with room for optional extra fields,
> e.g.: something like a two-tuple of (heads_dict, type_advice), which
> looks like:
>
> heads_dict: {'tip': [revid], 'tags': [revid, revid, …]}
> type_advice: {'tip': ['must fetch'], 'tags': ['optional', 'ghost ok']}
>
> (Probably an actual formal object would be better, but you get the idea)

> Foreign implementations could add new things into the data returned,
> e.g. 'loom threads', and advise whether they are must-fetch or not,
> etc.
That would give us more flexibility, but I'm not sure what that would
actually gain us. It means the caller has to be aware of the kinds of
things that are being returned, and it makes the API harder to use (as
you'd need to do post-processing on the return value). The separation
between just "always fetch" and "fetch if present" seems sufficient to
me.

> This would also resolve the XXX about fetching a different
> stop_revision than tip without potentially removing an important revid
> (if it's the tip and a loom thread, then replacing the 'tip' value
> won't remove it from the heads-to-fetch info entirely).
This discarding (if you mean the discarding in make_fetch_spec) no
longer happens now that stop_revision is passed into
Branch.heads_to_fetch.

Cheers,

Jelmer

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

...
>> (Probably an actual formal object would be better, but you get the idea)
>
>> Foreign implementations could add new things into the data returned,
>> e.g. 'loom threads', and advise whether they are must-fetch or not,
>> etc.
> That would give us more flexibility, but I'm not sure what that would
> actually gain us. It means the caller has to be aware of the kinds of
> things that are being returned, and it makes the API harder to use (as
> you'd need to do post-processing on the return value). The separation
> between just "always fetch" and "fetch if present" seems sufficient to
> me.

We also need a "fetch into a stacked branch if present in master
branch". We *might* just make that 'fetch if present'.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk2cP8kACgkQJdeBCYSNAANy+gCdECaVBBa2dWP19irl1+ua3fmc
fPMAn1Izq/Zlqwpovb6bOwMOXBQqZYp7
=fLp+
-----END PGP SIGNATURE-----

Revision history for this message
Andrew Bennetts (spiv) wrote :

Jelmer Vernooij wrote:
> Hi spiv,
>
> On Wed, 2011-04-06 at 05:51 +0000, Andrew Bennetts wrote:
> > Review: Needs Fixing
>
> > I'm confused: this patch still has fetch_tags on
> > Branch.fetch and InterBranch.fetch. Wasn't the revised plan that these
> > would only take stop_revision and no other args?
> See my comment earlier: Branch.update_revisions takes a fetch_tags
> argument, hence we still needed fetch_tags/include_tags as an argument.
> Alternatively, we could consider dropping that argument to
> update_revisions too, I'm not sure where it's used.

It's used in exactly 2 places, I think: BzrBranch._basic_push and
GenericInterBranch._pull, and I don't think it's really possible to remove
stop_revision from those.

I don't think that matters though, because update_revisions could simply
use self.target.repository.fetch instead of self.target.fetch. By the
time it invokes fetch it's already determined what it wants to fetch and
can just deal with repository directly.

[…]
> > It makes me wonder if heads_to_fetch should actually take no args but
> > return something more structured, with room for optional extra fields,
> > e.g.: something like a two-tuple of (heads_dict, type_advice), which
> > looks like:
> >
> > heads_dict: {'tip': [revid], 'tags': [revid, revid, …]}
> > type_advice: {'tip': ['must fetch'], 'tags': ['optional', 'ghost ok']}
> >
> > (Probably an actual formal object would be better, but you get the idea)
>
> > Foreign implementations could add new things into the data returned,
> > e.g. 'loom threads', and advise whether they are must-fetch or not,
> > etc.
> That would give us more flexibility, but I'm not sure what that would
> actually gain us. It means the caller has to be aware of the kinds of
> things that are being returned, and it makes the API harder to use (as
> you'd need to do post-processing on the return value). The separation
> between just "always fetch" and "fetch if present" seems sufficient to
> me.

Well, if we returned an object rather than just dicts the object could
have methods to take care of the post-processing.

> > This would also resolve the XXX about fetching a different
> > stop_revision than tip without potentially removing an important revid
> > (if it's the tip and a loom thread, then replacing the 'tip' value
> > won't remove it from the heads-to-fetch info entirely).
> This discarding (if you mean the discarding in make_fetch_spec) no
> longer happens now that stop_revision is passed into
> Branch.heads_to_fetch.

Yes, that's the discarding I mean. I realise that passing stop_revision
to heads_to_fetch avoids that problem, but I don't much like doing that
:)

Revision history for this message
Martin Pool (mbp) wrote :

Based on spiv's last comment, I'm going to set this back to wip, just to get it off the queue. Please push it back whenever you want more review or help.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/branch.py'
2--- bzrlib/branch.py 2011-04-19 13:56:32 +0000
3+++ bzrlib/branch.py 2011-04-20 19:55:54 +0000
4@@ -669,20 +669,15 @@
5 raise errors.UnsupportedOperation(self.get_reference_info, self)
6
7 @needs_write_lock
8- def fetch(self, from_branch, last_revision=None, fetch_spec=None):
9+ def fetch(self, from_branch, last_revision=None):
10 """Copy revisions from from_branch into this branch.
11
12 :param from_branch: Where to copy from.
13 :param last_revision: What revision to stop at (None for at the end
14 of the branch.
15- :param fetch_spec: If specified, a SearchResult or
16- PendingAncestryResult that describes which revisions to copy. This
17- allows copying multiple heads at once. Mutually exclusive with
18- last_revision.
19 :return: None
20 """
21- return InterBranch.get(from_branch, self).fetch(last_revision,
22- fetch_spec)
23+ return InterBranch.get(from_branch, self).fetch(last_revision)
24
25 def get_bound_location(self):
26 """Return the URL of the branch we are bound to.
27@@ -1000,7 +995,7 @@
28 return (0, _mod_revision.NULL_REVISION)
29
30 def update_revisions(self, other, stop_revision=None, overwrite=False,
31- graph=None, fetch_tags=True):
32+ graph=None):
33 """Pull in new perfect-fit revisions.
34
35 :param other: Another Branch to pull from
36@@ -1009,12 +1004,10 @@
37 to see if it is a proper descendant.
38 :param graph: A Graph object that can be used to query history
39 information. This can be None.
40- :param fetch_tags: Flag that specifies if tags from other should be
41- fetched too.
42 :return: None
43 """
44 return InterBranch.get(other, self).update_revisions(stop_revision,
45- overwrite, graph, fetch_tags=fetch_tags)
46+ overwrite, graph)
47
48 @deprecated_method(deprecated_in((2, 4, 0)))
49 def import_last_revision_info(self, source_repo, revno, revid):
50@@ -1045,14 +1038,7 @@
51 (should only be different from the arguments when lossy=True)
52 """
53 if not self.repository.has_same_location(source.repository):
54- try:
55- tags_to_fetch = set(source.tags.get_reverse_tag_dict())
56- except errors.TagsNotSupported:
57- tags_to_fetch = set()
58- fetch_spec = _mod_graph.NotInOtherForRevs(self.repository,
59- source.repository, [revid],
60- if_present_ids=tags_to_fetch).execute()
61- self.repository.fetch(source.repository, fetch_spec=fetch_spec)
62+ self.fetch(source, revid)
63 self.set_last_revision_info(revno, revid)
64 return (revno, revid)
65
66@@ -1535,18 +1521,22 @@
67 else:
68 raise AssertionError("invalid heads: %r" % (heads,))
69
70- def heads_to_fetch(self):
71+ def heads_to_fetch(self, stop_revision=None):
72 """Return the heads that must and that should be fetched to copy this
73 branch into another repo.
74
75+ :param stop_revision: Last branch revision to fetch, if not tip.
76+
77 :returns: a 2-tuple of (must_fetch, if_present_fetch). must_fetch is a
78 set of heads that must be fetched. if_present_fetch is a set of
79 heads that must be fetched if present, but no error is necessary if
80 they are not present.
81 """
82- # For bzr native formats must_fetch is just the tip, and if_present_fetch
83- # are the tags.
84- must_fetch = set([self.last_revision()])
85+ # For bzr native formats must_fetch is just the tip, and
86+ # if_present_fetch are the tags.
87+ if stop_revision is None:
88+ stop_revision = self.last_revision()
89+ must_fetch = set([stop_revision])
90 try:
91 if_present_fetch = set(self.tags.get_reverse_tag_dict())
92 except errors.TagsNotSupported:
93@@ -3298,7 +3288,7 @@
94
95 @needs_write_lock
96 def update_revisions(self, stop_revision=None, overwrite=False,
97- graph=None, fetch_tags=True):
98+ graph=None):
99 """Pull in new perfect-fit revisions.
100
101 :param stop_revision: Updated until the given revision
102@@ -3306,8 +3296,6 @@
103 to see if it is a proper descendant.
104 :param graph: A Graph object that can be used to query history
105 information. This can be None.
106- :param fetch_tags: Flag that specifies if tags from source should be
107- fetched too.
108 :return: None
109 """
110 raise NotImplementedError(self.update_revisions)
111@@ -3331,11 +3319,10 @@
112 raise NotImplementedError(self.copy_content_into)
113
114 @needs_write_lock
115- def fetch(self, stop_revision=None, fetch_spec=None):
116+ def fetch(self, stop_revision=None):
117 """Fetch revisions.
118
119 :param stop_revision: Last revision to fetch
120- :param fetch_spec: Fetch spec.
121 """
122 raise NotImplementedError(self.fetch)
123
124@@ -3379,25 +3366,26 @@
125 self.source.tags.merge_to(self.target.tags)
126
127 @needs_write_lock
128- def fetch(self, stop_revision=None, fetch_spec=None):
129- if fetch_spec is not None and stop_revision is not None:
130- raise AssertionError(
131- "fetch_spec and last_revision are mutually exclusive.")
132+ def fetch(self, stop_revision=None):
133 if self.target.base == self.source.base:
134 return (0, [])
135 self.source.lock_read()
136 try:
137- if stop_revision is None and fetch_spec is None:
138- stop_revision = self.source.last_revision()
139- stop_revision = _mod_revision.ensure_null(stop_revision)
140+ fetch_spec_factory = fetch.FetchSpecFactory()
141+ fetch_spec_factory.source_branch = self.source
142+ fetch_spec_factory.source_branch_stop_revision_id = stop_revision
143+ fetch_spec_factory.source_repo = self.source.repository
144+ fetch_spec_factory.target_repo = self.target.repository
145+ fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
146+ fetch_spec = fetch_spec_factory.make_fetch_spec()
147 return self.target.repository.fetch(self.source.repository,
148- revision_id=stop_revision, fetch_spec=fetch_spec)
149+ fetch_spec=fetch_spec)
150 finally:
151 self.source.unlock()
152
153 @needs_write_lock
154 def update_revisions(self, stop_revision=None, overwrite=False,
155- graph=None, fetch_tags=True):
156+ graph=None):
157 """See InterBranch.update_revisions()."""
158 other_revno, other_last_revision = self.source.last_revision_info()
159 stop_revno = None # unknown
160@@ -3415,18 +3403,7 @@
161 # case of having something to pull, and so that the check for
162 # already merged can operate on the just fetched graph, which will
163 # be cached in memory.
164- if fetch_tags:
165- fetch_spec_factory = fetch.FetchSpecFactory()
166- fetch_spec_factory.source_branch = self.source
167- fetch_spec_factory.source_branch_stop_revision_id = stop_revision
168- fetch_spec_factory.source_repo = self.source.repository
169- fetch_spec_factory.target_repo = self.target.repository
170- fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
171- fetch_spec = fetch_spec_factory.make_fetch_spec()
172- else:
173- fetch_spec = _mod_graph.NotInOtherForRevs(self.target.repository,
174- self.source.repository, revision_ids=[stop_revision]).execute()
175- self.target.fetch(self.source, fetch_spec=fetch_spec)
176+ self.fetch(stop_revision=stop_revision)
177 # Check to see if one is an ancestor of the other
178 if not overwrite:
179 if graph is None:
180
181=== modified file 'bzrlib/fetch.py'
182--- bzrlib/fetch.py 2011-04-17 23:06:22 +0000
183+++ bzrlib/fetch.py 2011-04-20 19:55:54 +0000
184@@ -375,7 +375,7 @@
185 def add_revision_ids(self, revision_ids):
186 """Add revision_ids to the set of revision_ids to be fetched."""
187 self._explicit_rev_ids.update(revision_ids)
188-
189+
190 def make_fetch_spec(self):
191 """Build a SearchResult or PendingAncestryResult or etc."""
192 if self.target_repo_kind is None or self.source_repo is None:
193@@ -392,15 +392,8 @@
194 self.target_repo, self.source_repo).execute()
195 heads_to_fetch = set(self._explicit_rev_ids)
196 if self.source_branch is not None:
197- must_fetch, if_present_fetch = self.source_branch.heads_to_fetch()
198- if self.source_branch_stop_revision_id is not None:
199- # Replace the tip rev from must_fetch with the stop revision
200- # XXX: this might be wrong if the tip rev is also in the
201- # must_fetch set for other reasons (e.g. it's the tip of
202- # multiple loom threads?), but then it's pretty unclear what it
203- # should mean to specify a stop_revision in that case anyway.
204- must_fetch.discard(self.source_branch.last_revision())
205- must_fetch.add(self.source_branch_stop_revision_id)
206+ must_fetch, if_present_fetch = self.source_branch.heads_to_fetch(
207+ stop_revision=self.source_branch_stop_revision_id)
208 heads_to_fetch.update(must_fetch)
209 else:
210 if_present_fetch = set()
211
212=== modified file 'bzrlib/merge.py'
213--- bzrlib/merge.py 2011-04-17 23:06:22 +0000
214+++ bzrlib/merge.py 2011-04-20 19:55:54 +0000
215@@ -563,14 +563,7 @@
216
217 def _maybe_fetch(self, source, target, revision_id):
218 if not source.repository.has_same_location(target.repository):
219- try:
220- tags_to_fetch = set(source.tags.get_reverse_tag_dict())
221- except errors.TagsNotSupported:
222- tags_to_fetch = None
223- fetch_spec = _mod_graph.NotInOtherForRevs(target.repository,
224- source.repository, [revision_id],
225- if_present_ids=tags_to_fetch).execute()
226- target.fetch(source, fetch_spec=fetch_spec)
227+ target.fetch(source, revision_id)
228
229 def find_base(self):
230 revisions = [_mod_revision.ensure_null(self.this_basis),
231
232=== modified file 'bzrlib/remote.py'
233--- bzrlib/remote.py 2011-04-19 13:56:32 +0000
234+++ bzrlib/remote.py 2011-04-20 19:55:54 +0000
235@@ -3046,32 +3046,33 @@
236 self._ensure_real()
237 return self._real_branch.set_push_location(location)
238
239- def heads_to_fetch(self):
240+ def heads_to_fetch(self, stop_revision=None):
241 if self._format._use_default_local_heads_to_fetch():
242 # We recognise this format, and its heads-to-fetch implementation
243 # is the default one (tip + tags). In this case it's cheaper to
244 # just use the default implementation rather than a special RPC as
245 # the tip and tags data is cached.
246- return branch.Branch.heads_to_fetch(self)
247+ return branch.Branch.heads_to_fetch(self, stop_revision)
248 medium = self._client._medium
249 if medium._is_remote_before((2, 4)):
250- return self._vfs_heads_to_fetch()
251+ return self._vfs_heads_to_fetch(stop_revision)
252 try:
253- return self._rpc_heads_to_fetch()
254+ return self._rpc_heads_to_fetch(stop_revision)
255 except errors.UnknownSmartMethod:
256 medium._remember_remote_is_before((2, 4))
257- return self._vfs_heads_to_fetch()
258+ return self._vfs_heads_to_fetch(stop_revision)
259
260- def _rpc_heads_to_fetch(self):
261- response = self._call('Branch.heads_to_fetch', self._remote_path())
262+ def _rpc_heads_to_fetch(self, stop_revision):
263+ response = self._call('Branch.heads_to_fetch', self._remote_path(),
264+ stop_revision)
265 if len(response) != 2:
266 raise errors.UnexpectedSmartServerResponse(response)
267 must_fetch, if_present_fetch = response
268 return set(must_fetch), set(if_present_fetch)
269
270- def _vfs_heads_to_fetch(self):
271+ def _vfs_heads_to_fetch(self, stop_revision):
272 self._ensure_real()
273- return self._real_branch.heads_to_fetch()
274+ return self._real_branch.heads_to_fetch(stop_revision)
275
276
277 class RemoteConfig(object):
278
279=== modified file 'bzrlib/smart/branch.py'
280--- bzrlib/smart/branch.py 2011-02-25 04:44:53 +0000
281+++ bzrlib/smart/branch.py 2011-04-20 19:55:54 +0000
282@@ -144,14 +144,14 @@
283
284 class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
285
286- def do_with_branch(self, branch):
287+ def do_with_branch(self, branch, stop_revision):
288 """Return the heads-to-fetch for a Branch as two bencoded lists.
289
290 See Branch.heads_to_fetch.
291
292 New in 2.4.
293 """
294- must_fetch, if_present_fetch = branch.heads_to_fetch()
295+ must_fetch, if_present_fetch = branch.heads_to_fetch(stop_revision)
296 return SuccessfulSmartServerResponse(
297 (list(must_fetch), list(if_present_fetch)))
298
299
300=== modified file 'bzrlib/tests/per_branch/test_branch.py'
301--- bzrlib/tests/per_branch/test_branch.py 2011-04-14 07:53:38 +0000
302+++ bzrlib/tests/per_branch/test_branch.py 2011-04-20 19:55:54 +0000
303@@ -476,6 +476,14 @@
304 must_fetch, should_fetch = tree.branch.heads_to_fetch()
305 self.assertTrue('rev2' in must_fetch)
306
307+ def test_heads_to_fetch_stop_revision(self):
308+ tree = self.make_branch_and_tree('a')
309+ tree.commit('first commit', rev_id='rev1')
310+ tree.commit('second commit', rev_id='rev2')
311+ must_fetch, should_fetch = tree.branch.heads_to_fetch('rev1')
312+ self.assertTrue('rev1' in must_fetch)
313+ self.assertFalse('rev2' in must_fetch)
314+
315 def test_heads_to_fetch_not_null_revision(self):
316 # NULL_REVISION does not appear in the result of heads_to_fetch, even
317 # for an empty branch.
318
319=== modified file 'bzrlib/tests/test_remote.py'
320--- bzrlib/tests/test_remote.py 2011-04-05 17:37:53 +0000
321+++ bzrlib/tests/test_remote.py 2011-04-20 19:55:54 +0000
322@@ -1203,7 +1203,7 @@
323 'Branch.get_stacked_on_url', ('quack/',),
324 'error', ('NotStacked',))
325 client.add_expected_call(
326- 'Branch.heads_to_fetch', ('quack/',),
327+ 'Branch.heads_to_fetch', ('quack/', None),
328 'success', (['tip'], ['tagged-1', 'tagged-2']))
329 transport.mkdir('quack')
330 transport = transport.clone('quack')
331
332=== modified file 'bzrlib/tests/test_revisionspec.py'
333--- bzrlib/tests/test_revisionspec.py 2011-04-14 07:53:38 +0000
334+++ bzrlib/tests/test_revisionspec.py 2011-04-20 19:55:54 +0000
335@@ -397,8 +397,7 @@
336 """We can get any revision id in the repository"""
337 # XXX: This may change in the future, but for now, it is true
338 self.tree2.commit('alt third', rev_id='alt_r3')
339- self.tree.branch.repository.fetch(self.tree2.branch.repository,
340- revision_id='alt_r3')
341+ self.tree.branch.fetch(self.tree2.branch, 'alt_r3')
342 self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
343
344 def test_unicode(self):
345@@ -475,8 +474,7 @@
346 def test_alt_no_parents(self):
347 new_tree = self.make_branch_and_tree('new_tree')
348 new_tree.commit('first', rev_id='new_r1')
349- self.tree.branch.repository.fetch(new_tree.branch.repository,
350- revision_id='new_r1')
351+ self.tree.branch.fetch(new_tree.branch, 'new_r1')
352 self.assertInHistoryIs(0, 'null:', 'before:revid:new_r1')
353
354 def test_as_revision_id(self):
355
356=== modified file 'bzrlib/workingtree_4.py'
357--- bzrlib/workingtree_4.py 2011-04-17 23:06:22 +0000
358+++ bzrlib/workingtree_4.py 2011-04-20 19:55:54 +0000
359@@ -2016,7 +2016,7 @@
360 def make_source_parent_tree(source, target):
361 """Change the source tree into a parent of the target."""
362 revid = source.commit('record tree')
363- target.branch.repository.fetch(source.branch.repository, revid)
364+ target.branch.fetch(source.branch, revid)
365 target.set_parent_ids([revid])
366 return target.basis_tree(), target
367
368
369=== modified file 'doc/en/release-notes/bzr-2.4.txt'
370--- doc/en/release-notes/bzr-2.4.txt 2011-04-19 14:12:43 +0000
371+++ doc/en/release-notes/bzr-2.4.txt 2011-04-20 19:55:54 +0000
372@@ -357,7 +357,7 @@
373 (Jelmer Vernooij)
374
375 * ``Branch.fetch`` implementations must now accept an optional
376- ``fetch_spec`` keyword argument. (Andrew Bennetts)
377+ ``fetch_tags`` keyword argument. (Andrew Bennetts)
378
379 * ``Branch.import_last_revision_info`` is deprecated. Use the
380 ``import_last_revision_info_and_tags`` method instead.