/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/git/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-13 23:57:28 UTC
  • mfrom: (7490 work)
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200213235728-m6ds0mm3mbs4y182
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
"""An adapter between a Git Branch and a Bazaar Branch"""
19
19
 
 
20
from __future__ import absolute_import
20
21
 
21
22
import contextlib
22
23
from io import BytesIO
41
42
    lock,
42
43
    repository as _mod_repository,
43
44
    revision,
 
45
    tag,
44
46
    trace,
45
47
    transport,
46
48
    urlutils,
49
51
from ..revision import (
50
52
    NULL_REVISION,
51
53
    )
52
 
from ..tag import (
53
 
    Tags,
54
 
    InterTags,
55
 
    )
56
54
from ..trace import (
57
55
    is_quiet,
58
56
    mutter,
59
57
    warning,
60
58
    )
61
59
 
 
60
from .config import (
 
61
    GitBranchConfig,
 
62
    GitBranchStack,
 
63
    )
62
64
from .errors import (
63
65
    NoPushSupport,
64
66
    )
65
 
from .mapping import (
66
 
    encode_git_path,
67
 
    decode_git_path,
68
 
    )
69
67
from .push import (
70
68
    remote_divergence,
71
69
    )
114
112
        return self._lookup_revno(self.new_revid)
115
113
 
116
114
 
117
 
class InterTagsFromGitToRemoteGit(InterTags):
118
 
 
119
 
    @classmethod
120
 
    def is_compatible(klass, source, target):
121
 
        if not isinstance(source, GitTags):
122
 
            return False
123
 
        if not isinstance(target, GitTags):
124
 
            return False
125
 
        if getattr(target.branch.repository, "_git", None) is not None:
126
 
            return False
127
 
        return True
128
 
 
129
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
130
 
        if self.source.branch.repository.has_same_location(self.target.branch.repository):
131
 
            return {}, []
 
115
class GitTags(tag.BasicTags):
 
116
    """Ref-based tag dictionary."""
 
117
 
 
118
    def __init__(self, branch):
 
119
        self.branch = branch
 
120
        self.repository = branch.repository
 
121
 
 
122
    def _merge_to_remote_git(self, target_repo, source_tag_refs,
 
123
                             overwrite=False):
132
124
        updates = {}
133
125
        conflicts = []
134
 
        source_tag_refs = self.source.branch.get_tag_refs()
135
 
        ref_to_tag_map = {}
136
126
 
137
127
        def get_changed_refs(old_refs):
138
128
            ret = dict(old_refs)
139
129
            for ref_name, tag_name, peeled, unpeeled in (
140
130
                    source_tag_refs.iteritems()):
141
 
                if selector and not selector(tag_name):
142
 
                    continue
143
131
                if old_refs.get(ref_name) == unpeeled:
144
132
                    pass
145
133
                elif overwrite or ref_name not in old_refs:
146
134
                    ret[ref_name] = unpeeled
147
 
                    updates[tag_name] = self.target.branch.repository.lookup_foreign_revision_id(
 
135
                    updates[tag_name] = target_repo.lookup_foreign_revision_id(
148
136
                        peeled)
149
 
                    ref_to_tag_map[ref_name] = tag_name
150
 
                    self.target.branch._tag_refs = None
151
137
                else:
152
138
                    conflicts.append(
153
139
                        (tag_name,
154
140
                         self.repository.lookup_foreign_revision_id(peeled),
155
 
                         self.target.branch.repository.lookup_foreign_revision_id(
 
141
                         target_repo.lookup_foreign_revision_id(
156
142
                             old_refs[ref_name])))
157
143
            return ret
158
 
        result = self.target.branch.repository.controldir.send_pack(
 
144
        target_repo.controldir.send_pack(
159
145
            get_changed_refs, lambda have, want: [])
160
 
        if result is not None and not isinstance(result, dict):
161
 
            for ref, error in result.ref_status.items():
162
 
                if error:
163
 
                    warning('unable to update ref %s: %s',
164
 
                            ref, error)
165
 
                    del updates[ref_to_tag_map[ref]]
166
 
        return updates, set(conflicts)
167
 
 
168
 
 
169
 
class InterTagsFromGitToLocalGit(InterTags):
170
 
 
171
 
    @classmethod
172
 
    def is_compatible(klass, source, target):
173
 
        if not isinstance(source, GitTags):
174
 
            return False
175
 
        if not isinstance(target, GitTags):
176
 
            return False
177
 
        if getattr(target.branch.repository, "_git", None) is None:
178
 
            return False
179
 
        return True
180
 
 
181
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
182
 
        if self.source.branch.repository.has_same_location(self.target.branch.repository):
183
 
            return {}, []
184
 
 
 
146
        return updates, conflicts
 
147
 
 
148
    def _merge_to_local_git(self, target_repo, source_tag_refs,
 
149
                            overwrite=False):
185
150
        conflicts = []
186
151
        updates = {}
187
 
        source_tag_refs = self.source.branch.get_tag_refs()
188
 
 
189
 
        target_repo = self.target.branch.repository
190
 
 
191
152
        for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
192
 
            if selector and not selector(tag_name):
193
 
                continue
194
153
            if target_repo._git.refs.get(ref_name) == unpeeled:
195
154
                pass
196
155
            elif overwrite or ref_name not in target_repo._git.refs:
206
165
                                  tag_name)
207
166
                    continue
208
167
                target_repo._git.refs[ref_name] = unpeeled or peeled
209
 
                self.target.branch._tag_refs = None
210
168
            else:
211
169
                try:
212
 
                    source_revid = self.source.branch.repository.lookup_foreign_revision_id(
 
170
                    source_revid = self.repository.lookup_foreign_revision_id(
213
171
                        peeled)
214
172
                    target_revid = target_repo.lookup_foreign_revision_id(
215
173
                        target_repo._git.refs[ref_name])
222
180
                                  tag_name)
223
181
                    continue
224
182
                conflicts.append((tag_name, source_revid, target_revid))
225
 
        return updates, set(conflicts)
226
 
 
227
 
 
228
 
class InterTagsFromGitToNonGit(InterTags):
229
 
 
230
 
    @classmethod
231
 
    def is_compatible(klass, source, target):
232
 
        if not isinstance(source, GitTags):
233
 
            return False
234
 
        if isinstance(target, GitTags):
235
 
            return False
236
 
        return True
237
 
 
238
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
239
 
        """See Tags.merge_to."""
240
 
        source_tag_refs = self.source.branch.get_tag_refs()
241
 
        if ignore_master:
242
 
            master = None
243
 
        else:
244
 
            master = self.target.branch.get_master_branch()
245
 
        with contextlib.ExitStack() as es:
246
 
            if master is not None:
247
 
                es.enter_context(master.lock_write())
248
 
            updates, conflicts = self._merge_to(
249
 
                self.target, source_tag_refs, overwrite=overwrite,
250
 
                selector=selector)
251
 
            if master is not None:
252
 
                extra_updates, extra_conflicts = self._merge_to(
253
 
                    master.tags, overwrite=overwrite,
254
 
                    source_tag_refs=source_tag_refs,
255
 
                    ignore_master=ignore_master, selector=selector)
256
 
                updates.update(extra_updates)
257
 
                conflicts.update(extra_conflicts)
258
 
            return updates, conflicts
259
 
 
260
 
    def _merge_to(self, to_tags, source_tag_refs, overwrite=False,
261
 
                  selector=None):
 
183
        return updates, conflicts
 
184
 
 
185
    def _merge_to_git(self, to_tags, source_tag_refs, overwrite=False):
 
186
        target_repo = to_tags.repository
 
187
        if self.repository.has_same_location(target_repo):
 
188
            return {}, []
 
189
        try:
 
190
            if getattr(target_repo, "_git", None):
 
191
                return self._merge_to_local_git(
 
192
                    target_repo, source_tag_refs, overwrite)
 
193
            else:
 
194
                return self._merge_to_remote_git(
 
195
                    target_repo, source_tag_refs, overwrite)
 
196
        finally:
 
197
            to_tags.branch._tag_refs = None
 
198
 
 
199
    def _merge_to_non_git(self, to_tags, source_tag_refs, overwrite=False):
262
200
        unpeeled_map = defaultdict(set)
263
201
        conflicts = []
264
202
        updates = {}
265
203
        result = dict(to_tags.get_tag_dict())
266
204
        for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
267
 
            if selector and not selector(tag_name):
268
 
                continue
269
205
            if unpeeled is not None:
270
206
                unpeeled_map[peeled].add(unpeeled)
271
207
            try:
272
 
                bzr_revid = self.source.branch.lookup_foreign_revision_id(peeled)
 
208
                bzr_revid = self.branch.lookup_foreign_revision_id(peeled)
273
209
            except NotCommitError:
274
210
                continue
275
211
            if result.get(tag_name) == bzr_revid:
284
220
            map_file = UnpeelMap.from_repository(to_tags.branch.repository)
285
221
            map_file.update(unpeeled_map)
286
222
            map_file.save_in_repository(to_tags.branch.repository)
287
 
        return updates, set(conflicts)
288
 
 
289
 
 
290
 
InterTags.register_optimiser(InterTagsFromGitToRemoteGit)
291
 
InterTags.register_optimiser(InterTagsFromGitToLocalGit)
292
 
InterTags.register_optimiser(InterTagsFromGitToNonGit)
293
 
 
294
 
 
295
 
class GitTags(Tags):
296
 
    """Ref-based tag dictionary."""
297
 
 
298
 
    def __init__(self, branch):
299
 
        self.branch = branch
300
 
        self.repository = branch.repository
 
223
        return updates, conflicts
 
224
 
 
225
    def merge_to(self, to_tags, overwrite=False, ignore_master=False,
 
226
                 source_tag_refs=None):
 
227
        """See Tags.merge_to."""
 
228
        if source_tag_refs is None:
 
229
            source_tag_refs = self.branch.get_tag_refs()
 
230
        if self == to_tags:
 
231
            return {}, []
 
232
        if isinstance(to_tags, GitTags):
 
233
            return self._merge_to_git(to_tags, source_tag_refs,
 
234
                                      overwrite=overwrite)
 
235
        else:
 
236
            if ignore_master:
 
237
                master = None
 
238
            else:
 
239
                master = to_tags.branch.get_master_branch()
 
240
            with contextlib.ExitStack() as es:
 
241
                if master is not None:
 
242
                    es.enter_context(master.lock_write())
 
243
                updates, conflicts = self._merge_to_non_git(
 
244
                    to_tags, source_tag_refs, overwrite=overwrite)
 
245
                if master is not None:
 
246
                    extra_updates, extra_conflicts = self.merge_to(
 
247
                        master.tags, overwrite=overwrite,
 
248
                        source_tag_refs=source_tag_refs,
 
249
                        ignore_master=ignore_master)
 
250
                    updates.update(extra_updates)
 
251
                    conflicts += extra_conflicts
 
252
                return updates, conflicts
301
253
 
302
254
    def get_tag_dict(self):
303
255
        ret = {}
311
263
                ret[tag_name] = bzr_revid
312
264
        return ret
313
265
 
314
 
    def lookup_tag(self, tag_name):
315
 
        """Return the referent string of a tag"""
316
 
        # TODO(jelmer): Replace with something more efficient for local tags.
317
 
        td = self.get_tag_dict()
318
 
        try:
319
 
            return td[tag_name]
320
 
        except KeyError:
321
 
            raise errors.NoSuchTag(tag_name)
322
 
 
323
266
 
324
267
class LocalGitTagDict(GitTags):
325
268
    """Dictionary with tags in a local repository."""
482
425
        return "git"
483
426
 
484
427
    def get_config(self):
485
 
        from .config import GitBranchConfig
486
428
        return GitBranchConfig(self)
487
429
 
488
430
    def get_config_stack(self):
489
 
        from .config import GitBranchStack
490
431
        return GitBranchStack(self)
491
432
 
492
433
    def _get_nick(self, local=False, possible_master_transports=None):
498
439
            cs = self.repository._git.get_config_stack()
499
440
            try:
500
441
                return cs.get((b"branch", self.name.encode('utf-8')),
501
 
                        b"nick").decode("utf-8")
 
442
                              b"nick").decode("utf-8")
502
443
            except KeyError:
503
444
                pass
504
445
        return self.name or u"HEAD"
672
613
                return revision.NULL_REVISION
673
614
            return self.lookup_foreign_revision_id(self.head)
674
615
 
675
 
    def _basic_push(self, target, overwrite=False, stop_revision=None,
676
 
                    tag_selector=None):
 
616
    def _basic_push(self, target, overwrite=False, stop_revision=None):
677
617
        return branch.InterBranch.get(self, target)._basic_push(
678
 
            overwrite, stop_revision, tag_selector=tag_selector)
 
618
            overwrite, stop_revision)
679
619
 
680
620
    def lookup_foreign_revision_id(self, foreign_revid):
681
621
        try:
995
935
            stop_revision, fetch_tags=fetch_tags, limit=limit, lossy=lossy)
996
936
        return _mod_repository.FetchResult()
997
937
 
998
 
    def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False, tag_selector=None):
 
938
    def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False):
999
939
        interrepo = self._get_interrepo(self.source, self.target)
1000
940
        if fetch_tags is None:
1001
941
            c = self.source.get_config_stack()
1013
953
            else:
1014
954
                self._last_revid = stop_revision
1015
955
            real = interrepo.get_determine_wants_revids(
1016
 
                [self._last_revid], include_tags=fetch_tags, tag_selector=tag_selector)
 
956
                [self._last_revid], include_tags=fetch_tags)
1017
957
            return real(heads)
1018
958
        pack_hint, head, refs = interrepo.fetch_objects(
1019
959
            determine_wants, self.source.mapping, limit=limit,
1023
963
            self.target.repository.pack(hint=pack_hint)
1024
964
        return head, refs
1025
965
 
1026
 
    def _update_revisions(self, stop_revision=None, overwrite=False, tag_selector=None):
1027
 
        head, refs = self.fetch_objects(stop_revision, fetch_tags=None, tag_selector=tag_selector)
 
966
    def _update_revisions(self, stop_revision=None, overwrite=False):
 
967
        head, refs = self.fetch_objects(stop_revision, fetch_tags=None)
1028
968
        if overwrite:
1029
969
            prev_last_revid = None
1030
970
        else:
1043
983
                for path, url, section in parse_submodules(
1044
984
                        GitConfigFile.from_file(f)):
1045
985
                    self.target.set_reference_info(
1046
 
                        tree.path2id(decode_git_path(path)), url.decode('utf-8'),
1047
 
                        decode_git_path(path))
 
986
                        tree.path2id(path.decode('utf-8')), url.decode('utf-8'),
 
987
                        path.decode('utf-8'))
1048
988
        except errors.NoSuchFile:
1049
989
            pass
1050
990
 
1051
991
    def _basic_pull(self, stop_revision, overwrite, run_hooks,
1052
 
                    _override_hook_target, _hook_master, tag_selector=None):
 
992
                    _override_hook_target, _hook_master):
1053
993
        if overwrite is True:
1054
994
            overwrite = set(["history", "tags"])
1055
995
        elif not overwrite:
1066
1006
            (result.old_revno, result.old_revid) = \
1067
1007
                self.target.last_revision_info()
1068
1008
            result.new_git_head, remote_refs = self._update_revisions(
1069
 
                stop_revision, overwrite=("history" in overwrite),
1070
 
                tag_selector=tag_selector)
 
1009
                stop_revision, overwrite=("history" in overwrite))
1071
1010
            tags_ret = self.source.tags.merge_to(
1072
1011
                self.target.tags, ("tags" in overwrite), ignore_master=True)
1073
1012
            if isinstance(tags_ret, tuple):
1090
1029
 
1091
1030
    def pull(self, overwrite=False, stop_revision=None,
1092
1031
             possible_transports=None, _hook_master=None, run_hooks=True,
1093
 
             _override_hook_target=None, local=False, tag_selector=None):
 
1032
             _override_hook_target=None, local=False):
1094
1033
        """See Branch.pull.
1095
1034
 
1096
1035
        :param _hook_master: Private parameter - set the branch to
1128
1067
                master_branch = None
1129
1068
            return self._basic_pull(stop_revision, overwrite, run_hooks,
1130
1069
                                    _override_hook_target,
1131
 
                                    _hook_master=master_branch,
1132
 
                                    tag_selector=tag_selector)
 
1070
                                    _hook_master=master_branch)
1133
1071
 
1134
 
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
 
1072
    def _basic_push(self, overwrite, stop_revision):
1135
1073
        if overwrite is True:
1136
1074
            overwrite = set(["history", "tags"])
1137
1075
        elif not overwrite:
1141
1079
        result.target_branch = self.target
1142
1080
        result.old_revno, result.old_revid = self.target.last_revision_info()
1143
1081
        result.new_git_head, remote_refs = self._update_revisions(
1144
 
            stop_revision, overwrite=("history" in overwrite),
1145
 
            tag_selector=tag_selector)
 
1082
            stop_revision, overwrite=("history" in overwrite))
1146
1083
        tags_ret = self.source.tags.merge_to(
1147
 
            self.target.tags, "tags" in overwrite, ignore_master=True,
1148
 
            selector=tag_selector)
 
1084
            self.target.tags, "tags" in overwrite, ignore_master=True)
1149
1085
        (result.tag_updates, result.tag_conflicts) = tags_ret
1150
1086
        result.new_revno, result.new_revid = self.target.last_revision_info()
1151
1087
        self.update_references(revid=result.new_revid)
1174
1110
        return (isinstance(source, LocalGitBranch) and
1175
1111
                isinstance(target, RemoteGitBranch))
1176
1112
 
1177
 
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
1178
 
        from .remote import parse_git_error
 
1113
    def _basic_push(self, overwrite, stop_revision):
1179
1114
        result = GitBranchPushResult()
1180
1115
        result.source_branch = self.source
1181
1116
        result.target_branch = self.target
1200
1135
            result.new_revid = stop_revision
1201
1136
            for name, sha in (
1202
1137
                    self.source.repository._git.refs.as_dict(b"refs/tags").items()):
1203
 
                if tag_selector and not tag_selector(name):
1204
 
                    continue
1205
1138
                if sha not in self.source.repository._git:
1206
1139
                    trace.mutter('Ignoring missing SHA: %s', sha)
1207
1140
                    continue
1208
1141
                refs[tag_name_to_ref(name)] = sha
1209
1142
            return refs
1210
 
        dw_result = self.target.repository.send_pack(
 
1143
        self.target.repository.send_pack(
1211
1144
            get_changed_refs,
1212
 
            self.source.repository._git.generate_pack_data)
1213
 
        if dw_result is not None and not isinstance(dw_result, dict):
1214
 
            error = dw_result.ref_status.get(self.target.ref)
1215
 
            if error:
1216
 
                raise parse_git_error(self.target.user_url, error)
1217
 
            for ref, error in dw_result.ref_status.items():
1218
 
                if error:
1219
 
                    trace.warning('unable to open ref %s: %s', ref, error)
 
1145
            self.source.repository._git.object_store.generate_pack_data)
1220
1146
        return result
1221
1147
 
1222
1148
 
1248
1174
        interrepo.fetch_objects(determine_wants, limit=limit, lossy=lossy)
1249
1175
        return _mod_repository.FetchResult()
1250
1176
 
1251
 
    def _basic_push(self, overwrite=False, stop_revision=None, tag_selector=None):
 
1177
    def _basic_push(self, overwrite=False, stop_revision=None):
1252
1178
        if overwrite is True:
1253
1179
            overwrite = set(["history", "tags"])
1254
1180
        elif not overwrite:
1264
1190
            other_branch=self.source)
1265
1191
        tags_ret = self.source.tags.merge_to(
1266
1192
            self.target.tags,
1267
 
            overwrite=("tags" in overwrite),
1268
 
            selector=tag_selector)
 
1193
            source_tag_refs=remote_refs_dict_to_tag_refs(refs),
 
1194
            overwrite=("tags" in overwrite))
1269
1195
        if isinstance(tags_ret, tuple):
1270
1196
            (result.tag_updates, result.tag_conflicts) = tags_ret
1271
1197
        else:
1293
1219
        return result.refs, stop_revision
1294
1220
 
1295
1221
    def pull(self, stop_revision=None, overwrite=False,
1296
 
             possible_transports=None, run_hooks=True, local=False,
1297
 
             tag_selector=None):
 
1222
             possible_transports=None, run_hooks=True, local=False):
1298
1223
        # This type of branch can't be bound.
1299
1224
        if local:
1300
1225
            raise errors.LocalRequiresBoundBranch()
1315
1240
                other_branch=self.source)
1316
1241
            tags_ret = self.source.tags.merge_to(
1317
1242
                self.target.tags, overwrite=("tags" in overwrite),
1318
 
                selector=tag_selector)
 
1243
                source_tag_refs=remote_refs_dict_to_tag_refs(refs))
1319
1244
            if isinstance(tags_ret, tuple):
1320
1245
                (result.tag_updates, result.tag_conflicts) = tags_ret
1321
1246
            else:
1383
1308
                    refs[ref] = (None, revid)
1384
1309
        return refs, main_ref, (stop_revno, stop_revision)
1385
1310
 
1386
 
    def _update_refs(self, result, old_refs, new_refs, overwrite, tag_selector):
 
1311
    def _update_refs(self, result, old_refs, new_refs, overwrite):
1387
1312
        mutter("updating refs. old refs: %r, new refs: %r",
1388
1313
               old_refs, new_refs)
1389
1314
        result.tag_updates = {}
1390
1315
        result.tag_conflicts = []
1391
 
        ret = {}
 
1316
        ret = dict(old_refs)
1392
1317
 
1393
1318
        def ref_equals(refs, ref, git_sha, revid):
1394
1319
            try:
1422
1347
                except ValueError:
1423
1348
                    pass
1424
1349
                else:
1425
 
                    if tag_selector and not tag_selector(tag_name):
1426
 
                        continue
1427
1350
                    result.tag_updates[tag_name] = revid
1428
1351
                ret[ref] = (git_sha, revid)
1429
1352
            else:
1459
1382
            for (old_revid, (new_sha, new_revid)) in revidmap.items()})
1460
1383
 
1461
1384
    def pull(self, overwrite=False, stop_revision=None, local=False,
1462
 
             possible_transports=None, run_hooks=True, _stop_revno=None,
1463
 
             tag_selector=None):
 
1385
             possible_transports=None, run_hooks=True, _stop_revno=None):
1464
1386
        result = GitBranchPullResult()
1465
1387
        result.source_branch = self.source
1466
1388
        result.target_branch = self.target
1469
1391
                stop_revision, stop_revno=_stop_revno)
1470
1392
 
1471
1393
            def update_refs(old_refs):
1472
 
                return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
 
1394
                return self._update_refs(result, old_refs, new_refs, overwrite)
1473
1395
            try:
1474
1396
                result.revidmap, old_refs, new_refs = (
1475
1397
                    self.interrepo.fetch_refs(update_refs, lossy=False))
1489
1411
        return result
1490
1412
 
1491
1413
    def push(self, overwrite=False, stop_revision=None, lossy=False,
1492
 
             _override_hook_source_branch=None, _stop_revno=None,
1493
 
             tag_selector=None):
 
1414
             _override_hook_source_branch=None, _stop_revno=None):
1494
1415
        result = GitBranchPushResult()
1495
1416
        result.source_branch = self.source
1496
1417
        result.target_branch = self.target
1501
1422
                stop_revision, stop_revno=_stop_revno)
1502
1423
 
1503
1424
            def update_refs(old_refs):
1504
 
                return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
 
1425
                return self._update_refs(result, old_refs, new_refs, overwrite)
1505
1426
            try:
1506
1427
                result.revidmap, old_refs, new_refs = (
1507
1428
                    self.interrepo.fetch_refs(