|
1 # Copyright 2017 Facebook, Inc. |
|
2 # |
|
3 # This software may be used and distributed according to the terms of the |
|
4 # GNU General Public License version 2 or any later version. |
|
5 |
|
6 from __future__ import absolute_import |
|
7 |
|
8 from mercurial.i18n import _ |
|
9 |
|
10 from mercurial import ( |
|
11 bundle2, |
|
12 changegroup, |
|
13 error, |
|
14 extensions, |
|
15 revsetlang, |
|
16 util, |
|
17 ) |
|
18 |
|
19 from . import common |
|
20 |
|
21 encodebookmarks = common.encodebookmarks |
|
22 isremotebooksenabled = common.isremotebooksenabled |
|
23 |
|
24 scratchbranchparttype = 'b2x:infinitepush' |
|
25 scratchbookmarksparttype = 'b2x:infinitepushscratchbookmarks' |
|
26 |
|
27 def getscratchbranchparts(repo, peer, outgoing, confignonforwardmove, |
|
28 ui, bookmark, create): |
|
29 if not outgoing.missing: |
|
30 raise error.Abort(_('no commits to push')) |
|
31 |
|
32 if scratchbranchparttype not in bundle2.bundle2caps(peer): |
|
33 raise error.Abort(_('no server support for %r') % scratchbranchparttype) |
|
34 |
|
35 _validaterevset(repo, revsetlang.formatspec('%ln', outgoing.missing), |
|
36 bookmark) |
|
37 |
|
38 supportedversions = changegroup.supportedoutgoingversions(repo) |
|
39 # Explicitly avoid using '01' changegroup version in infinitepush to |
|
40 # support general delta |
|
41 supportedversions.discard('01') |
|
42 cgversion = min(supportedversions) |
|
43 _handlelfs(repo, outgoing.missing) |
|
44 cg = changegroup.makestream(repo, outgoing, cgversion, 'push') |
|
45 |
|
46 params = {} |
|
47 params['cgversion'] = cgversion |
|
48 if bookmark: |
|
49 params['bookmark'] = bookmark |
|
50 # 'prevbooknode' is necessary for pushkey reply part |
|
51 params['bookprevnode'] = '' |
|
52 if bookmark in repo: |
|
53 params['bookprevnode'] = repo[bookmark].hex() |
|
54 if create: |
|
55 params['create'] = '1' |
|
56 if confignonforwardmove: |
|
57 params['force'] = '1' |
|
58 |
|
59 # Do not send pushback bundle2 part with bookmarks if remotenames extension |
|
60 # is enabled. It will be handled manually in `_push()` |
|
61 if not isremotebooksenabled(ui): |
|
62 params['pushbackbookmarks'] = '1' |
|
63 |
|
64 parts = [] |
|
65 |
|
66 # .upper() marks this as a mandatory part: server will abort if there's no |
|
67 # handler |
|
68 parts.append(bundle2.bundlepart( |
|
69 scratchbranchparttype.upper(), |
|
70 advisoryparams=params.iteritems(), |
|
71 data=cg)) |
|
72 |
|
73 try: |
|
74 treemod = extensions.find('treemanifest') |
|
75 mfnodes = [] |
|
76 for node in outgoing.missing: |
|
77 mfnodes.append(('', repo[node].manifestnode())) |
|
78 |
|
79 # Only include the tree parts if they all exist |
|
80 if not repo.manifestlog.datastore.getmissing(mfnodes): |
|
81 parts.append(treemod.createtreepackpart( |
|
82 repo, outgoing, treemod.TREEGROUP_PARTTYPE2)) |
|
83 except KeyError: |
|
84 pass |
|
85 |
|
86 return parts |
|
87 |
|
88 def getscratchbookmarkspart(peer, bookmarks): |
|
89 if scratchbookmarksparttype not in bundle2.bundle2caps(peer): |
|
90 raise error.Abort( |
|
91 _('no server support for %r') % scratchbookmarksparttype) |
|
92 |
|
93 return bundle2.bundlepart( |
|
94 scratchbookmarksparttype.upper(), |
|
95 data=encodebookmarks(bookmarks)) |
|
96 |
|
97 def _validaterevset(repo, revset, bookmark): |
|
98 """Abort if the revs to be pushed aren't valid for a scratch branch.""" |
|
99 if not repo.revs(revset): |
|
100 raise error.Abort(_('nothing to push')) |
|
101 if bookmark: |
|
102 # Allow bundle with many heads only if no bookmark is specified |
|
103 heads = repo.revs('heads(%r)', revset) |
|
104 if len(heads) > 1: |
|
105 raise error.Abort( |
|
106 _('cannot push more than one head to a scratch branch')) |
|
107 |
|
108 def _handlelfs(repo, missing): |
|
109 '''Special case if lfs is enabled |
|
110 |
|
111 If lfs is enabled then we need to call prepush hook |
|
112 to make sure large files are uploaded to lfs |
|
113 ''' |
|
114 try: |
|
115 lfsmod = extensions.find('lfs') |
|
116 lfsmod.wrapper.uploadblobsfromrevs(repo, missing) |
|
117 except KeyError: |
|
118 # Ignore if lfs extension is not enabled |
|
119 return |
|
120 |
|
121 class copiedpart(object): |
|
122 """a copy of unbundlepart content that can be consumed later""" |
|
123 |
|
124 def __init__(self, part): |
|
125 # copy "public properties" |
|
126 self.type = part.type |
|
127 self.id = part.id |
|
128 self.mandatory = part.mandatory |
|
129 self.mandatoryparams = part.mandatoryparams |
|
130 self.advisoryparams = part.advisoryparams |
|
131 self.params = part.params |
|
132 self.mandatorykeys = part.mandatorykeys |
|
133 # copy the buffer |
|
134 self._io = util.stringio(part.read()) |
|
135 |
|
136 def consume(self): |
|
137 return |
|
138 |
|
139 def read(self, size=None): |
|
140 if size is None: |
|
141 return self._io.read() |
|
142 else: |
|
143 return self._io.read(size) |