##// END OF EJS Templates
git: show the version of `pygit2` with verbose version output...
Matt Harbison -
r46562:c7c1efdf default
parent child Browse files
Show More
@@ -1,340 +1,343
1 """grant Mercurial the ability to operate on Git repositories. (EXPERIMENTAL)
1 """grant Mercurial the ability to operate on Git repositories. (EXPERIMENTAL)
2
2
3 This is currently super experimental. It probably will consume your
3 This is currently super experimental. It probably will consume your
4 firstborn a la Rumpelstiltskin, etc.
4 firstborn a la Rumpelstiltskin, etc.
5 """
5 """
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 from mercurial import (
13 from mercurial import (
14 commands,
14 commands,
15 error,
15 error,
16 extensions,
16 extensions,
17 localrepo,
17 localrepo,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 scmutil,
20 scmutil,
21 store,
21 store,
22 util,
22 util,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 dirstate,
26 dirstate,
27 gitlog,
27 gitlog,
28 gitutil,
28 gitutil,
29 index,
29 index,
30 )
30 )
31
31
32 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
32 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 # be specifying the version(s) of Mercurial they are tested with, or
34 # be specifying the version(s) of Mercurial they are tested with, or
35 # leave the attribute unspecified.
35 # leave the attribute unspecified.
36 testedwith = b'ships-with-hg-core'
36 testedwith = b'ships-with-hg-core'
37
37
38 configtable = {}
38 configtable = {}
39 configitem = registrar.configitem(configtable)
39 configitem = registrar.configitem(configtable)
40 # git.log-index-cache-miss: internal knob for testing
40 # git.log-index-cache-miss: internal knob for testing
41 configitem(
41 configitem(
42 b"git",
42 b"git",
43 b"log-index-cache-miss",
43 b"log-index-cache-miss",
44 default=False,
44 default=False,
45 )
45 )
46
46
47 getversion = gitutil.pygit2_version
48
49
47 # TODO: extract an interface for this in core
50 # TODO: extract an interface for this in core
48 class gitstore(object): # store.basicstore):
51 class gitstore(object): # store.basicstore):
49 def __init__(self, path, vfstype):
52 def __init__(self, path, vfstype):
50 self.vfs = vfstype(path)
53 self.vfs = vfstype(path)
51 self.path = self.vfs.base
54 self.path = self.vfs.base
52 self.createmode = store._calcmode(self.vfs)
55 self.createmode = store._calcmode(self.vfs)
53 # above lines should go away in favor of:
56 # above lines should go away in favor of:
54 # super(gitstore, self).__init__(path, vfstype)
57 # super(gitstore, self).__init__(path, vfstype)
55
58
56 self.git = gitutil.get_pygit2().Repository(
59 self.git = gitutil.get_pygit2().Repository(
57 os.path.normpath(os.path.join(path, b'..', b'.git'))
60 os.path.normpath(os.path.join(path, b'..', b'.git'))
58 )
61 )
59 self._progress_factory = lambda *args, **kwargs: None
62 self._progress_factory = lambda *args, **kwargs: None
60 self._logfn = lambda x: None
63 self._logfn = lambda x: None
61
64
62 @util.propertycache
65 @util.propertycache
63 def _db(self):
66 def _db(self):
64 # We lazy-create the database because we want to thread a
67 # We lazy-create the database because we want to thread a
65 # progress callback down to the indexing process if it's
68 # progress callback down to the indexing process if it's
66 # required, and we don't have a ui handle in makestore().
69 # required, and we don't have a ui handle in makestore().
67 return index.get_index(self.git, self._logfn, self._progress_factory)
70 return index.get_index(self.git, self._logfn, self._progress_factory)
68
71
69 def join(self, f):
72 def join(self, f):
70 """Fake store.join method for git repositories.
73 """Fake store.join method for git repositories.
71
74
72 For the most part, store.join is used for @storecache
75 For the most part, store.join is used for @storecache
73 decorators to invalidate caches when various files
76 decorators to invalidate caches when various files
74 change. We'll map the ones we care about, and ignore the rest.
77 change. We'll map the ones we care about, and ignore the rest.
75 """
78 """
76 if f in (b'00changelog.i', b'00manifest.i'):
79 if f in (b'00changelog.i', b'00manifest.i'):
77 # This is close enough: in order for the changelog cache
80 # This is close enough: in order for the changelog cache
78 # to be invalidated, HEAD will have to change.
81 # to be invalidated, HEAD will have to change.
79 return os.path.join(self.path, b'HEAD')
82 return os.path.join(self.path, b'HEAD')
80 elif f == b'lock':
83 elif f == b'lock':
81 # TODO: we probably want to map this to a git lock, I
84 # TODO: we probably want to map this to a git lock, I
82 # suspect index.lock. We should figure out what the
85 # suspect index.lock. We should figure out what the
83 # most-alike file is in git-land. For now we're risking
86 # most-alike file is in git-land. For now we're risking
84 # bad concurrency errors if another git client is used.
87 # bad concurrency errors if another git client is used.
85 return os.path.join(self.path, b'hgit-bogus-lock')
88 return os.path.join(self.path, b'hgit-bogus-lock')
86 elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
89 elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
87 return os.path.join(self.path, b'..', b'.hg', f)
90 return os.path.join(self.path, b'..', b'.hg', f)
88 raise NotImplementedError(b'Need to pick file for %s.' % f)
91 raise NotImplementedError(b'Need to pick file for %s.' % f)
89
92
90 def changelog(self, trypending):
93 def changelog(self, trypending):
91 # TODO we don't have a plan for trypending in hg's git support yet
94 # TODO we don't have a plan for trypending in hg's git support yet
92 return gitlog.changelog(self.git, self._db)
95 return gitlog.changelog(self.git, self._db)
93
96
94 def manifestlog(self, repo, storenarrowmatch):
97 def manifestlog(self, repo, storenarrowmatch):
95 # TODO handle storenarrowmatch and figure out if we need the repo arg
98 # TODO handle storenarrowmatch and figure out if we need the repo arg
96 return gitlog.manifestlog(self.git, self._db)
99 return gitlog.manifestlog(self.git, self._db)
97
100
98 def invalidatecaches(self):
101 def invalidatecaches(self):
99 pass
102 pass
100
103
101 def write(self, tr=None):
104 def write(self, tr=None):
102 # normally this handles things like fncache writes, which we don't have
105 # normally this handles things like fncache writes, which we don't have
103 pass
106 pass
104
107
105
108
106 def _makestore(orig, requirements, storebasepath, vfstype):
109 def _makestore(orig, requirements, storebasepath, vfstype):
107 if b'git' in requirements:
110 if b'git' in requirements:
108 if not os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
111 if not os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
109 raise error.Abort(
112 raise error.Abort(
110 _(
113 _(
111 b'repository specified git format in '
114 b'repository specified git format in '
112 b'.hg/requires but has no .git directory'
115 b'.hg/requires but has no .git directory'
113 )
116 )
114 )
117 )
115 # Check for presence of pygit2 only here. The assumption is that we'll
118 # Check for presence of pygit2 only here. The assumption is that we'll
116 # run this code iff we'll later need pygit2.
119 # run this code iff we'll later need pygit2.
117 if gitutil.get_pygit2() is None:
120 if gitutil.get_pygit2() is None:
118 raise error.Abort(
121 raise error.Abort(
119 _(
122 _(
120 b'the git extension requires the Python '
123 b'the git extension requires the Python '
121 b'pygit2 library to be installed'
124 b'pygit2 library to be installed'
122 )
125 )
123 )
126 )
124
127
125 return gitstore(storebasepath, vfstype)
128 return gitstore(storebasepath, vfstype)
126 return orig(requirements, storebasepath, vfstype)
129 return orig(requirements, storebasepath, vfstype)
127
130
128
131
129 class gitfilestorage(object):
132 class gitfilestorage(object):
130 def file(self, path):
133 def file(self, path):
131 if path[0:1] == b'/':
134 if path[0:1] == b'/':
132 path = path[1:]
135 path = path[1:]
133 return gitlog.filelog(self.store.git, self.store._db, path)
136 return gitlog.filelog(self.store.git, self.store._db, path)
134
137
135
138
136 def _makefilestorage(orig, requirements, features, **kwargs):
139 def _makefilestorage(orig, requirements, features, **kwargs):
137 store = kwargs['store']
140 store = kwargs['store']
138 if isinstance(store, gitstore):
141 if isinstance(store, gitstore):
139 return gitfilestorage
142 return gitfilestorage
140 return orig(requirements, features, **kwargs)
143 return orig(requirements, features, **kwargs)
141
144
142
145
143 def _setupdothg(ui, path):
146 def _setupdothg(ui, path):
144 dothg = os.path.join(path, b'.hg')
147 dothg = os.path.join(path, b'.hg')
145 if os.path.exists(dothg):
148 if os.path.exists(dothg):
146 ui.warn(_(b'git repo already initialized for hg\n'))
149 ui.warn(_(b'git repo already initialized for hg\n'))
147 else:
150 else:
148 os.mkdir(os.path.join(path, b'.hg'))
151 os.mkdir(os.path.join(path, b'.hg'))
149 # TODO is it ok to extend .git/info/exclude like this?
152 # TODO is it ok to extend .git/info/exclude like this?
150 with open(
153 with open(
151 os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
154 os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
152 ) as exclude:
155 ) as exclude:
153 exclude.write(b'\n.hg\n')
156 exclude.write(b'\n.hg\n')
154 with open(os.path.join(dothg, b'requires'), 'wb') as f:
157 with open(os.path.join(dothg, b'requires'), 'wb') as f:
155 f.write(b'git\n')
158 f.write(b'git\n')
156
159
157
160
158 _BMS_PREFIX = 'refs/heads/'
161 _BMS_PREFIX = 'refs/heads/'
159
162
160
163
161 class gitbmstore(object):
164 class gitbmstore(object):
162 def __init__(self, gitrepo):
165 def __init__(self, gitrepo):
163 self.gitrepo = gitrepo
166 self.gitrepo = gitrepo
164 self._aclean = True
167 self._aclean = True
165 self._active = gitrepo.references['HEAD'] # git head, not mark
168 self._active = gitrepo.references['HEAD'] # git head, not mark
166
169
167 def __contains__(self, name):
170 def __contains__(self, name):
168 return (
171 return (
169 _BMS_PREFIX + pycompat.fsdecode(name)
172 _BMS_PREFIX + pycompat.fsdecode(name)
170 ) in self.gitrepo.references
173 ) in self.gitrepo.references
171
174
172 def __iter__(self):
175 def __iter__(self):
173 for r in self.gitrepo.listall_references():
176 for r in self.gitrepo.listall_references():
174 if r.startswith(_BMS_PREFIX):
177 if r.startswith(_BMS_PREFIX):
175 yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
178 yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
176
179
177 def __getitem__(self, k):
180 def __getitem__(self, k):
178 return (
181 return (
179 self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
182 self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
180 .peel()
183 .peel()
181 .id.raw
184 .id.raw
182 )
185 )
183
186
184 def get(self, k, default=None):
187 def get(self, k, default=None):
185 try:
188 try:
186 if k in self:
189 if k in self:
187 return self[k]
190 return self[k]
188 return default
191 return default
189 except gitutil.get_pygit2().InvalidSpecError:
192 except gitutil.get_pygit2().InvalidSpecError:
190 return default
193 return default
191
194
192 @property
195 @property
193 def active(self):
196 def active(self):
194 h = self.gitrepo.references['HEAD']
197 h = self.gitrepo.references['HEAD']
195 if not isinstance(h.target, str) or not h.target.startswith(
198 if not isinstance(h.target, str) or not h.target.startswith(
196 _BMS_PREFIX
199 _BMS_PREFIX
197 ):
200 ):
198 return None
201 return None
199 return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
202 return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
200
203
201 @active.setter
204 @active.setter
202 def active(self, mark):
205 def active(self, mark):
203 githead = mark is not None and (_BMS_PREFIX + mark) or None
206 githead = mark is not None and (_BMS_PREFIX + mark) or None
204 if githead is not None and githead not in self.gitrepo.references:
207 if githead is not None and githead not in self.gitrepo.references:
205 raise AssertionError(b'bookmark %s does not exist!' % mark)
208 raise AssertionError(b'bookmark %s does not exist!' % mark)
206
209
207 self._active = githead
210 self._active = githead
208 self._aclean = False
211 self._aclean = False
209
212
210 def _writeactive(self):
213 def _writeactive(self):
211 if self._aclean:
214 if self._aclean:
212 return
215 return
213 self.gitrepo.references.create('HEAD', self._active, True)
216 self.gitrepo.references.create('HEAD', self._active, True)
214 self._aclean = True
217 self._aclean = True
215
218
216 def names(self, node):
219 def names(self, node):
217 r = []
220 r = []
218 for ref in self.gitrepo.listall_references():
221 for ref in self.gitrepo.listall_references():
219 if not ref.startswith(_BMS_PREFIX):
222 if not ref.startswith(_BMS_PREFIX):
220 continue
223 continue
221 if self.gitrepo.references[ref].peel().id.raw != node:
224 if self.gitrepo.references[ref].peel().id.raw != node:
222 continue
225 continue
223 r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
226 r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
224 return r
227 return r
225
228
226 # Cleanup opportunity: this is *identical* to core's bookmarks store.
229 # Cleanup opportunity: this is *identical* to core's bookmarks store.
227 def expandname(self, bname):
230 def expandname(self, bname):
228 if bname == b'.':
231 if bname == b'.':
229 if self.active:
232 if self.active:
230 return self.active
233 return self.active
231 raise error.RepoLookupError(_(b"no active bookmark"))
234 raise error.RepoLookupError(_(b"no active bookmark"))
232 return bname
235 return bname
233
236
234 def applychanges(self, repo, tr, changes):
237 def applychanges(self, repo, tr, changes):
235 """Apply a list of changes to bookmarks"""
238 """Apply a list of changes to bookmarks"""
236 # TODO: this should respect transactions, but that's going to
239 # TODO: this should respect transactions, but that's going to
237 # require enlarging the gitbmstore to know how to do in-memory
240 # require enlarging the gitbmstore to know how to do in-memory
238 # temporary writes and read those back prior to transaction
241 # temporary writes and read those back prior to transaction
239 # finalization.
242 # finalization.
240 for name, node in changes:
243 for name, node in changes:
241 if node is None:
244 if node is None:
242 self.gitrepo.references.delete(
245 self.gitrepo.references.delete(
243 _BMS_PREFIX + pycompat.fsdecode(name)
246 _BMS_PREFIX + pycompat.fsdecode(name)
244 )
247 )
245 else:
248 else:
246 self.gitrepo.references.create(
249 self.gitrepo.references.create(
247 _BMS_PREFIX + pycompat.fsdecode(name),
250 _BMS_PREFIX + pycompat.fsdecode(name),
248 gitutil.togitnode(node),
251 gitutil.togitnode(node),
249 force=True,
252 force=True,
250 )
253 )
251
254
252 def checkconflict(self, mark, force=False, target=None):
255 def checkconflict(self, mark, force=False, target=None):
253 githead = _BMS_PREFIX + mark
256 githead = _BMS_PREFIX + mark
254 cur = self.gitrepo.references['HEAD']
257 cur = self.gitrepo.references['HEAD']
255 if githead in self.gitrepo.references and not force:
258 if githead in self.gitrepo.references and not force:
256 if target:
259 if target:
257 if self.gitrepo.references[githead] == target and target == cur:
260 if self.gitrepo.references[githead] == target and target == cur:
258 # re-activating a bookmark
261 # re-activating a bookmark
259 return []
262 return []
260 # moving a bookmark - forward?
263 # moving a bookmark - forward?
261 raise NotImplementedError
264 raise NotImplementedError
262 raise error.Abort(
265 raise error.Abort(
263 _(b"bookmark '%s' already exists (use -f to force)") % mark
266 _(b"bookmark '%s' already exists (use -f to force)") % mark
264 )
267 )
265 if len(mark) > 3 and not force:
268 if len(mark) > 3 and not force:
266 try:
269 try:
267 shadowhash = scmutil.isrevsymbol(self._repo, mark)
270 shadowhash = scmutil.isrevsymbol(self._repo, mark)
268 except error.LookupError: # ambiguous identifier
271 except error.LookupError: # ambiguous identifier
269 shadowhash = False
272 shadowhash = False
270 if shadowhash:
273 if shadowhash:
271 self._repo.ui.warn(
274 self._repo.ui.warn(
272 _(
275 _(
273 b"bookmark %s matches a changeset hash\n"
276 b"bookmark %s matches a changeset hash\n"
274 b"(did you leave a -r out of an 'hg bookmark' "
277 b"(did you leave a -r out of an 'hg bookmark' "
275 b"command?)\n"
278 b"command?)\n"
276 )
279 )
277 % mark
280 % mark
278 )
281 )
279 return []
282 return []
280
283
281
284
282 def init(orig, ui, dest=b'.', **opts):
285 def init(orig, ui, dest=b'.', **opts):
283 if opts.get('git', False):
286 if opts.get('git', False):
284 path = os.path.abspath(dest)
287 path = os.path.abspath(dest)
285 # TODO: walk up looking for the git repo
288 # TODO: walk up looking for the git repo
286 _setupdothg(ui, path)
289 _setupdothg(ui, path)
287 return 0
290 return 0
288 return orig(ui, dest=dest, **opts)
291 return orig(ui, dest=dest, **opts)
289
292
290
293
291 def reposetup(ui, repo):
294 def reposetup(ui, repo):
292 if repo.local() and isinstance(repo.store, gitstore):
295 if repo.local() and isinstance(repo.store, gitstore):
293 orig = repo.__class__
296 orig = repo.__class__
294 repo.store._progress_factory = repo.ui.makeprogress
297 repo.store._progress_factory = repo.ui.makeprogress
295 if ui.configbool(b'git', b'log-index-cache-miss'):
298 if ui.configbool(b'git', b'log-index-cache-miss'):
296 repo.store._logfn = repo.ui.warn
299 repo.store._logfn = repo.ui.warn
297
300
298 class gitlocalrepo(orig):
301 class gitlocalrepo(orig):
299 def _makedirstate(self):
302 def _makedirstate(self):
300 # TODO narrow support here
303 # TODO narrow support here
301 return dirstate.gitdirstate(
304 return dirstate.gitdirstate(
302 self.ui, self.vfs.base, self.store.git
305 self.ui, self.vfs.base, self.store.git
303 )
306 )
304
307
305 def commit(self, *args, **kwargs):
308 def commit(self, *args, **kwargs):
306 ret = orig.commit(self, *args, **kwargs)
309 ret = orig.commit(self, *args, **kwargs)
307 if ret is None:
310 if ret is None:
308 # there was nothing to commit, so we should skip
311 # there was nothing to commit, so we should skip
309 # the index fixup logic we'd otherwise do.
312 # the index fixup logic we'd otherwise do.
310 return None
313 return None
311 tid = self.store.git[gitutil.togitnode(ret)].tree.id
314 tid = self.store.git[gitutil.togitnode(ret)].tree.id
312 # DANGER! This will flush any writes staged to the
315 # DANGER! This will flush any writes staged to the
313 # index in Git, but we're sidestepping the index in a
316 # index in Git, but we're sidestepping the index in a
314 # way that confuses git when we commit. Alas.
317 # way that confuses git when we commit. Alas.
315 self.store.git.index.read_tree(tid)
318 self.store.git.index.read_tree(tid)
316 self.store.git.index.write()
319 self.store.git.index.write()
317 return ret
320 return ret
318
321
319 @property
322 @property
320 def _bookmarks(self):
323 def _bookmarks(self):
321 return gitbmstore(self.store.git)
324 return gitbmstore(self.store.git)
322
325
323 repo.__class__ = gitlocalrepo
326 repo.__class__ = gitlocalrepo
324 return repo
327 return repo
325
328
326
329
327 def _featuresetup(ui, supported):
330 def _featuresetup(ui, supported):
328 # don't die on seeing a repo with the git requirement
331 # don't die on seeing a repo with the git requirement
329 supported |= {b'git'}
332 supported |= {b'git'}
330
333
331
334
332 def extsetup(ui):
335 def extsetup(ui):
333 extensions.wrapfunction(localrepo, b'makestore', _makestore)
336 extensions.wrapfunction(localrepo, b'makestore', _makestore)
334 extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
337 extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
335 # Inject --git flag for `hg init`
338 # Inject --git flag for `hg init`
336 entry = extensions.wrapcommand(commands.table, b'init', init)
339 entry = extensions.wrapcommand(commands.table, b'init', init)
337 entry[1].extend(
340 entry[1].extend(
338 [(b'', b'git', None, b'setup up a git repository instead of hg')]
341 [(b'', b'git', None, b'setup up a git repository instead of hg')]
339 )
342 )
340 localrepo.featuresetupfuncs.add(_featuresetup)
343 localrepo.featuresetupfuncs.add(_featuresetup)
@@ -1,40 +1,53
1 """utilities to assist in working with pygit2"""
1 """utilities to assist in working with pygit2"""
2 from __future__ import absolute_import
2 from __future__ import absolute_import
3
3
4 from mercurial.node import bin, hex, nullid
4 from mercurial.node import bin, hex, nullid
5
5
6 from mercurial import pycompat
6 from mercurial import pycompat
7
7
8 pygit2_module = None
8 pygit2_module = None
9
9
10
10
11 def get_pygit2():
11 def get_pygit2():
12 global pygit2_module
12 global pygit2_module
13 if pygit2_module is None:
13 if pygit2_module is None:
14 try:
14 try:
15 import pygit2 as pygit2_module
15 import pygit2 as pygit2_module
16
16
17 pygit2_module.InvalidSpecError
17 pygit2_module.InvalidSpecError
18 except (ImportError, AttributeError):
18 except (ImportError, AttributeError):
19 pass
19 pass
20 return pygit2_module
20 return pygit2_module
21
21
22
22
23 def pygit2_version():
24 mod = get_pygit2()
25 v = "N/A"
26
27 if mod:
28 try:
29 v = mod.__version__
30 except AttributeError:
31 pass
32
33 return b"(pygit2 %s)" % v.encode("utf-8")
34
35
23 def togitnode(n):
36 def togitnode(n):
24 """Wrapper to convert a Mercurial binary node to a unicode hexlified node.
37 """Wrapper to convert a Mercurial binary node to a unicode hexlified node.
25
38
26 pygit2 and sqlite both need nodes as strings, not bytes.
39 pygit2 and sqlite both need nodes as strings, not bytes.
27 """
40 """
28 assert len(n) == 20
41 assert len(n) == 20
29 return pycompat.sysstr(hex(n))
42 return pycompat.sysstr(hex(n))
30
43
31
44
32 def fromgitnode(n):
45 def fromgitnode(n):
33 """Opposite of togitnode."""
46 """Opposite of togitnode."""
34 assert len(n) == 40
47 assert len(n) == 40
35 if pycompat.ispy3:
48 if pycompat.ispy3:
36 return bin(n.encode('ascii'))
49 return bin(n.encode('ascii'))
37 return bin(n)
50 return bin(n)
38
51
39
52
40 nullgit = togitnode(nullid)
53 nullgit = togitnode(nullid)
@@ -1,354 +1,358
1 #require pygit2
1 #require pygit2
2
2
3 Setup:
3 Setup:
4 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
4 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
5 > GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
5 > GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
6 > GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
6 > GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
7 > GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
7 > GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
8 > GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
8 > GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
9 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
9 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
10 > count=10
10 > count=10
11 > gitcommit() {
11 > gitcommit() {
12 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000";
12 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000";
13 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
13 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
14 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
14 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
15 > count=`expr $count + 1`
15 > count=`expr $count + 1`
16 > }
16 > }
17
17
18
18
19 $ hg version -v --config extensions.git= | grep '^[E ]'
20 Enabled extensions:
21 git internal (pygit2 *) (glob)
22
19 Test auto-loading extension works:
23 Test auto-loading extension works:
20 $ mkdir nogit
24 $ mkdir nogit
21 $ cd nogit
25 $ cd nogit
22 $ mkdir .hg
26 $ mkdir .hg
23 $ echo git >> .hg/requires
27 $ echo git >> .hg/requires
24 $ hg status
28 $ hg status
25 abort: repository specified git format in .hg/requires but has no .git directory
29 abort: repository specified git format in .hg/requires but has no .git directory
26 [255]
30 [255]
27 $ git init
31 $ git init
28 Initialized empty Git repository in $TESTTMP/nogit/.git/
32 Initialized empty Git repository in $TESTTMP/nogit/.git/
29 This status invocation shows some hg gunk because we didn't use
33 This status invocation shows some hg gunk because we didn't use
30 `hg init --git`, which fixes up .git/info/exclude for us.
34 `hg init --git`, which fixes up .git/info/exclude for us.
31 $ hg status
35 $ hg status
32 ? .hg/cache/git-commits.sqlite
36 ? .hg/cache/git-commits.sqlite
33 ? .hg/cache/git-commits.sqlite-shm
37 ? .hg/cache/git-commits.sqlite-shm
34 ? .hg/cache/git-commits.sqlite-wal
38 ? .hg/cache/git-commits.sqlite-wal
35 ? .hg/requires
39 ? .hg/requires
36 $ cd ..
40 $ cd ..
37
41
38 Now globally enable extension for the rest of the test:
42 Now globally enable extension for the rest of the test:
39 $ cat <<EOF >> $HGRCPATH
43 $ cat <<EOF >> $HGRCPATH
40 > [extensions]
44 > [extensions]
41 > git=
45 > git=
42 > [git]
46 > [git]
43 > log-index-cache-miss = yes
47 > log-index-cache-miss = yes
44 > EOF
48 > EOF
45
49
46 Make a new repo with git:
50 Make a new repo with git:
47 $ mkdir foo
51 $ mkdir foo
48 $ cd foo
52 $ cd foo
49 $ git init
53 $ git init
50 Initialized empty Git repository in $TESTTMP/foo/.git/
54 Initialized empty Git repository in $TESTTMP/foo/.git/
51 Ignore the .hg directory within git:
55 Ignore the .hg directory within git:
52 $ echo .hg >> .git/info/exclude
56 $ echo .hg >> .git/info/exclude
53 $ echo alpha > alpha
57 $ echo alpha > alpha
54 $ git add alpha
58 $ git add alpha
55 $ gitcommit -am 'Add alpha'
59 $ gitcommit -am 'Add alpha'
56 $ echo beta > beta
60 $ echo beta > beta
57 $ git add beta
61 $ git add beta
58 $ gitcommit -am 'Add beta'
62 $ gitcommit -am 'Add beta'
59 $ echo gamma > gamma
63 $ echo gamma > gamma
60 $ git status
64 $ git status
61 On branch master
65 On branch master
62 Untracked files:
66 Untracked files:
63 (use "git add <file>..." to include in what will be committed)
67 (use "git add <file>..." to include in what will be committed)
64 gamma
68 gamma
65
69
66 nothing added to commit but untracked files present (use "git add" to track)
70 nothing added to commit but untracked files present (use "git add" to track)
67
71
68 Without creating the .hg, hg status fails:
72 Without creating the .hg, hg status fails:
69 $ hg status
73 $ hg status
70 abort: no repository found in '$TESTTMP/foo' (.hg not found)
74 abort: no repository found in '$TESTTMP/foo' (.hg not found)
71 [10]
75 [10]
72 But if you run hg init --git, it works:
76 But if you run hg init --git, it works:
73 $ hg init --git
77 $ hg init --git
74 $ hg id --traceback
78 $ hg id --traceback
75 heads mismatch, rebuilding dagcache
79 heads mismatch, rebuilding dagcache
76 3d9be8deba43 tip master
80 3d9be8deba43 tip master
77 $ hg status
81 $ hg status
78 ? gamma
82 ? gamma
79 Log works too:
83 Log works too:
80 $ hg log
84 $ hg log
81 changeset: 1:3d9be8deba43
85 changeset: 1:3d9be8deba43
82 bookmark: master
86 bookmark: master
83 tag: tip
87 tag: tip
84 user: test <test@example.org>
88 user: test <test@example.org>
85 date: Mon Jan 01 00:00:11 2007 +0000
89 date: Mon Jan 01 00:00:11 2007 +0000
86 summary: Add beta
90 summary: Add beta
87
91
88 changeset: 0:c5864c9d16fb
92 changeset: 0:c5864c9d16fb
89 user: test <test@example.org>
93 user: test <test@example.org>
90 date: Mon Jan 01 00:00:10 2007 +0000
94 date: Mon Jan 01 00:00:10 2007 +0000
91 summary: Add alpha
95 summary: Add alpha
92
96
93
97
94
98
95 and bookmarks:
99 and bookmarks:
96 $ hg bookmarks
100 $ hg bookmarks
97 * master 1:3d9be8deba43
101 * master 1:3d9be8deba43
98
102
99 diff even works transparently in both systems:
103 diff even works transparently in both systems:
100 $ echo blah >> alpha
104 $ echo blah >> alpha
101 $ git diff
105 $ git diff
102 diff --git a/alpha b/alpha
106 diff --git a/alpha b/alpha
103 index 4a58007..faed1b7 100644
107 index 4a58007..faed1b7 100644
104 --- a/alpha
108 --- a/alpha
105 +++ b/alpha
109 +++ b/alpha
106 @@ -1* +1,2 @@ (glob)
110 @@ -1* +1,2 @@ (glob)
107 alpha
111 alpha
108 +blah
112 +blah
109 $ hg diff --git
113 $ hg diff --git
110 diff --git a/alpha b/alpha
114 diff --git a/alpha b/alpha
111 --- a/alpha
115 --- a/alpha
112 +++ b/alpha
116 +++ b/alpha
113 @@ -1,1 +1,2 @@
117 @@ -1,1 +1,2 @@
114 alpha
118 alpha
115 +blah
119 +blah
116
120
117 Remove a file, it shows as such:
121 Remove a file, it shows as such:
118 $ rm alpha
122 $ rm alpha
119 $ hg status
123 $ hg status
120 ! alpha
124 ! alpha
121 ? gamma
125 ? gamma
122
126
123 Revert works:
127 Revert works:
124 $ hg revert alpha --traceback
128 $ hg revert alpha --traceback
125 $ hg status
129 $ hg status
126 ? gamma
130 ? gamma
127 $ git status
131 $ git status
128 On branch master
132 On branch master
129 Untracked files:
133 Untracked files:
130 (use "git add <file>..." to include in what will be committed)
134 (use "git add <file>..." to include in what will be committed)
131 gamma
135 gamma
132
136
133 nothing added to commit but untracked files present (use "git add" to track)
137 nothing added to commit but untracked files present (use "git add" to track)
134
138
135 Add shows sanely in both:
139 Add shows sanely in both:
136 $ hg add gamma
140 $ hg add gamma
137 $ hg status
141 $ hg status
138 A gamma
142 A gamma
139 $ hg files
143 $ hg files
140 alpha
144 alpha
141 beta
145 beta
142 gamma
146 gamma
143 $ git ls-files
147 $ git ls-files
144 alpha
148 alpha
145 beta
149 beta
146 gamma
150 gamma
147 $ git status
151 $ git status
148 On branch master
152 On branch master
149 Changes to be committed:
153 Changes to be committed:
150 (use "git restore --staged <file>..." to unstage)
154 (use "git restore --staged <file>..." to unstage)
151 new file: gamma
155 new file: gamma
152
156
153
157
154 forget does what it should as well:
158 forget does what it should as well:
155 $ hg forget gamma
159 $ hg forget gamma
156 $ hg status
160 $ hg status
157 ? gamma
161 ? gamma
158 $ git status
162 $ git status
159 On branch master
163 On branch master
160 Untracked files:
164 Untracked files:
161 (use "git add <file>..." to include in what will be committed)
165 (use "git add <file>..." to include in what will be committed)
162 gamma
166 gamma
163
167
164 nothing added to commit but untracked files present (use "git add" to track)
168 nothing added to commit but untracked files present (use "git add" to track)
165
169
166 clean up untracked file
170 clean up untracked file
167 $ rm gamma
171 $ rm gamma
168
172
169 hg log FILE
173 hg log FILE
170
174
171 $ echo a >> alpha
175 $ echo a >> alpha
172 $ hg ci -m 'more alpha' --traceback --date '1583522787 18000'
176 $ hg ci -m 'more alpha' --traceback --date '1583522787 18000'
173 $ echo b >> beta
177 $ echo b >> beta
174 $ hg ci -m 'more beta'
178 $ hg ci -m 'more beta'
175 heads mismatch, rebuilding dagcache
179 heads mismatch, rebuilding dagcache
176 $ echo a >> alpha
180 $ echo a >> alpha
177 $ hg ci -m 'even more alpha'
181 $ hg ci -m 'even more alpha'
178 heads mismatch, rebuilding dagcache
182 heads mismatch, rebuilding dagcache
179 $ hg log -G alpha
183 $ hg log -G alpha
180 heads mismatch, rebuilding dagcache
184 heads mismatch, rebuilding dagcache
181 @ changeset: 4:6626247b7dc8
185 @ changeset: 4:6626247b7dc8
182 : bookmark: master
186 : bookmark: master
183 : tag: tip
187 : tag: tip
184 : user: test <test>
188 : user: test <test>
185 : date: Thu Jan 01 00:00:00 1970 +0000
189 : date: Thu Jan 01 00:00:00 1970 +0000
186 : summary: even more alpha
190 : summary: even more alpha
187 :
191 :
188 o changeset: 2:a1983dd7fb19
192 o changeset: 2:a1983dd7fb19
189 : user: test <test>
193 : user: test <test>
190 : date: Fri Mar 06 14:26:27 2020 -0500
194 : date: Fri Mar 06 14:26:27 2020 -0500
191 : summary: more alpha
195 : summary: more alpha
192 :
196 :
193 o changeset: 0:c5864c9d16fb
197 o changeset: 0:c5864c9d16fb
194 user: test <test@example.org>
198 user: test <test@example.org>
195 date: Mon Jan 01 00:00:10 2007 +0000
199 date: Mon Jan 01 00:00:10 2007 +0000
196 summary: Add alpha
200 summary: Add alpha
197
201
198 $ hg log -G beta
202 $ hg log -G beta
199 o changeset: 3:d8ee22687733
203 o changeset: 3:d8ee22687733
200 : user: test <test>
204 : user: test <test>
201 : date: Thu Jan 01 00:00:00 1970 +0000
205 : date: Thu Jan 01 00:00:00 1970 +0000
202 : summary: more beta
206 : summary: more beta
203 :
207 :
204 o changeset: 1:3d9be8deba43
208 o changeset: 1:3d9be8deba43
205 | user: test <test@example.org>
209 | user: test <test@example.org>
206 ~ date: Mon Jan 01 00:00:11 2007 +0000
210 ~ date: Mon Jan 01 00:00:11 2007 +0000
207 summary: Add beta
211 summary: Add beta
208
212
209
213
210 $ hg log -r "children(3d9be8deba43)" -T"{node|short} {children}\n"
214 $ hg log -r "children(3d9be8deba43)" -T"{node|short} {children}\n"
211 a1983dd7fb19 3:d8ee22687733
215 a1983dd7fb19 3:d8ee22687733
212
216
213 hg annotate
217 hg annotate
214
218
215 $ hg annotate alpha
219 $ hg annotate alpha
216 0: alpha
220 0: alpha
217 2: a
221 2: a
218 4: a
222 4: a
219 $ hg annotate beta
223 $ hg annotate beta
220 1: beta
224 1: beta
221 3: b
225 3: b
222
226
223
227
224 Files in subdirectories. TODO: case-folding support, make this `A`
228 Files in subdirectories. TODO: case-folding support, make this `A`
225 instead of `a`.
229 instead of `a`.
226
230
227 $ mkdir a
231 $ mkdir a
228 $ echo "This is file mu." > a/mu
232 $ echo "This is file mu." > a/mu
229 $ hg ci -A -m 'Introduce file a/mu'
233 $ hg ci -A -m 'Introduce file a/mu'
230 adding a/mu
234 adding a/mu
231
235
232 Both hg and git agree a/mu is part of the repo
236 Both hg and git agree a/mu is part of the repo
233
237
234 $ git ls-files
238 $ git ls-files
235 a/mu
239 a/mu
236 alpha
240 alpha
237 beta
241 beta
238 $ hg files
242 $ hg files
239 a/mu
243 a/mu
240 alpha
244 alpha
241 beta
245 beta
242
246
243 hg and git status both clean
247 hg and git status both clean
244
248
245 $ git status
249 $ git status
246 On branch master
250 On branch master
247 nothing to commit, working tree clean
251 nothing to commit, working tree clean
248 $ hg status
252 $ hg status
249 heads mismatch, rebuilding dagcache
253 heads mismatch, rebuilding dagcache
250
254
251
255
252 node|shortest works correctly
256 node|shortest works correctly
253 $ hg log -T '{node}\n' | sort
257 $ hg log -T '{node}\n' | sort
254 3d9be8deba43482be2c81a4cb4be1f10d85fa8bc
258 3d9be8deba43482be2c81a4cb4be1f10d85fa8bc
255 6626247b7dc8f231b183b8a4761c89139baca2ad
259 6626247b7dc8f231b183b8a4761c89139baca2ad
256 a1983dd7fb19cbd83ad5a1c2fc8bf3d775dea12f
260 a1983dd7fb19cbd83ad5a1c2fc8bf3d775dea12f
257 ae1ab744f95bfd5b07cf573baef98a778058537b
261 ae1ab744f95bfd5b07cf573baef98a778058537b
258 c5864c9d16fb3431fe2c175ff84dc6accdbb2c18
262 c5864c9d16fb3431fe2c175ff84dc6accdbb2c18
259 d8ee22687733a1991813560b15128cd9734f4b48
263 d8ee22687733a1991813560b15128cd9734f4b48
260 $ hg log -r ae1ab744f95bfd5b07cf573baef98a778058537b --template "{shortest(node,1)}\n"
264 $ hg log -r ae1ab744f95bfd5b07cf573baef98a778058537b --template "{shortest(node,1)}\n"
261 ae
265 ae
262
266
263 This coveres changelog.findmissing()
267 This coveres changelog.findmissing()
264 $ hg merge --preview 3d9be8deba43
268 $ hg merge --preview 3d9be8deba43
265
269
266 This covers manifest.diff()
270 This covers manifest.diff()
267 $ hg diff -c 3d9be8deba43
271 $ hg diff -c 3d9be8deba43
268 diff -r c5864c9d16fb -r 3d9be8deba43 beta
272 diff -r c5864c9d16fb -r 3d9be8deba43 beta
269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 +++ b/beta Mon Jan 01 00:00:11 2007 +0000
274 +++ b/beta Mon Jan 01 00:00:11 2007 +0000
271 @@ -0,0 +1,1 @@
275 @@ -0,0 +1,1 @@
272 +beta
276 +beta
273
277
274
278
275 Interactive commit should work as expected
279 Interactive commit should work as expected
276
280
277 $ echo bar >> alpha
281 $ echo bar >> alpha
278 $ echo bar >> beta
282 $ echo bar >> beta
279 $ hg commit -m "test interactive commit" -i --config ui.interactive=true --config ui.interface=text << EOF
283 $ hg commit -m "test interactive commit" -i --config ui.interactive=true --config ui.interface=text << EOF
280 > y
284 > y
281 > y
285 > y
282 > n
286 > n
283 > EOF
287 > EOF
284 diff --git a/alpha b/alpha
288 diff --git a/alpha b/alpha
285 1 hunks, 1 lines changed
289 1 hunks, 1 lines changed
286 examine changes to 'alpha'?
290 examine changes to 'alpha'?
287 (enter ? for help) [Ynesfdaq?] y
291 (enter ? for help) [Ynesfdaq?] y
288
292
289 @@ -1,3 +1,4 @@
293 @@ -1,3 +1,4 @@
290 alpha
294 alpha
291 a
295 a
292 a
296 a
293 +bar
297 +bar
294 record change 1/2 to 'alpha'?
298 record change 1/2 to 'alpha'?
295 (enter ? for help) [Ynesfdaq?] y
299 (enter ? for help) [Ynesfdaq?] y
296
300
297 diff --git a/beta b/beta
301 diff --git a/beta b/beta
298 1 hunks, 1 lines changed
302 1 hunks, 1 lines changed
299 examine changes to 'beta'?
303 examine changes to 'beta'?
300 (enter ? for help) [Ynesfdaq?] n
304 (enter ? for help) [Ynesfdaq?] n
301
305
302 Status should be consistent for both systems
306 Status should be consistent for both systems
303
307
304 $ hg status
308 $ hg status
305 heads mismatch, rebuilding dagcache
309 heads mismatch, rebuilding dagcache
306 M beta
310 M beta
307 $ git status | egrep -v '^$|^ \(use '
311 $ git status | egrep -v '^$|^ \(use '
308 On branch master
312 On branch master
309 Changes not staged for commit:
313 Changes not staged for commit:
310 modified: beta
314 modified: beta
311 no changes added to commit (use "git add" and/or "git commit -a")
315 no changes added to commit (use "git add" and/or "git commit -a")
312
316
313 Contents of each commit should be the same
317 Contents of each commit should be the same
314
318
315 $ hg ex -r .
319 $ hg ex -r .
316 # HG changeset patch
320 # HG changeset patch
317 # User test <test>
321 # User test <test>
318 # Date 0 0
322 # Date 0 0
319 # Thu Jan 01 00:00:00 1970 +0000
323 # Thu Jan 01 00:00:00 1970 +0000
320 # Node ID 80adc61cf57e99f6a412d83fee6239d1556cefcf
324 # Node ID 80adc61cf57e99f6a412d83fee6239d1556cefcf
321 # Parent ae1ab744f95bfd5b07cf573baef98a778058537b
325 # Parent ae1ab744f95bfd5b07cf573baef98a778058537b
322 test interactive commit
326 test interactive commit
323
327
324 diff -r ae1ab744f95b -r 80adc61cf57e alpha
328 diff -r ae1ab744f95b -r 80adc61cf57e alpha
325 --- a/alpha Thu Jan 01 00:00:00 1970 +0000
329 --- a/alpha Thu Jan 01 00:00:00 1970 +0000
326 +++ b/alpha Thu Jan 01 00:00:00 1970 +0000
330 +++ b/alpha Thu Jan 01 00:00:00 1970 +0000
327 @@ -1,3 +1,4 @@
331 @@ -1,3 +1,4 @@
328 alpha
332 alpha
329 a
333 a
330 a
334 a
331 +bar
335 +bar
332 $ git show
336 $ git show
333 commit 80adc61cf57e99f6a412d83fee6239d1556cefcf
337 commit 80adc61cf57e99f6a412d83fee6239d1556cefcf
334 Author: test <test>
338 Author: test <test>
335 Date: Thu Jan 1 00:00:00 1970 +0000
339 Date: Thu Jan 1 00:00:00 1970 +0000
336
340
337 test interactive commit
341 test interactive commit
338
342
339 diff --git a/alpha b/alpha
343 diff --git a/alpha b/alpha
340 index d112a75..d2a2e9a 100644
344 index d112a75..d2a2e9a 100644
341 --- a/alpha
345 --- a/alpha
342 +++ b/alpha
346 +++ b/alpha
343 @@ -1,3 +1,4 @@
347 @@ -1,3 +1,4 @@
344 alpha
348 alpha
345 a
349 a
346 a
350 a
347 +bar
351 +bar
348
352
349 Deleting files should also work (this was issue6398)
353 Deleting files should also work (this was issue6398)
350 $ hg revert -r . --all
354 $ hg revert -r . --all
351 reverting beta
355 reverting beta
352 $ hg rm beta
356 $ hg rm beta
353 $ hg ci -m 'remove beta'
357 $ hg ci -m 'remove beta'
354
358
General Comments 0
You need to be logged in to leave comments. Login now