##// END OF EJS Templates
git: key off `git` in .hg/requires rather than separate file...
Augie Fackler -
r45362:02c47b74 default
parent child Browse files
Show More
@@ -1,266 +1,275
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 store,
19 store,
20 util,
20 util,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 dirstate,
24 dirstate,
25 gitlog,
25 gitlog,
26 gitutil,
26 gitutil,
27 index,
27 index,
28 )
28 )
29
29
30 # TODO: extract an interface for this in core
30 # TODO: extract an interface for this in core
31 class gitstore(object): # store.basicstore):
31 class gitstore(object): # store.basicstore):
32 def __init__(self, path, vfstype):
32 def __init__(self, path, vfstype):
33 self.vfs = vfstype(path)
33 self.vfs = vfstype(path)
34 self.path = self.vfs.base
34 self.path = self.vfs.base
35 self.createmode = store._calcmode(self.vfs)
35 self.createmode = store._calcmode(self.vfs)
36 # above lines should go away in favor of:
36 # above lines should go away in favor of:
37 # super(gitstore, self).__init__(path, vfstype)
37 # super(gitstore, self).__init__(path, vfstype)
38
38
39 self.git = gitutil.get_pygit2().Repository(
39 self.git = gitutil.get_pygit2().Repository(
40 os.path.normpath(os.path.join(path, b'..', b'.git'))
40 os.path.normpath(os.path.join(path, b'..', b'.git'))
41 )
41 )
42 self._progress_factory = lambda *args, **kwargs: None
42 self._progress_factory = lambda *args, **kwargs: None
43
43
44 @util.propertycache
44 @util.propertycache
45 def _db(self):
45 def _db(self):
46 # We lazy-create the database because we want to thread a
46 # We lazy-create the database because we want to thread a
47 # progress callback down to the indexing process if it's
47 # progress callback down to the indexing process if it's
48 # required, and we don't have a ui handle in makestore().
48 # required, and we don't have a ui handle in makestore().
49 return index.get_index(self.git, self._progress_factory)
49 return index.get_index(self.git, self._progress_factory)
50
50
51 def join(self, f):
51 def join(self, f):
52 """Fake store.join method for git repositories.
52 """Fake store.join method for git repositories.
53
53
54 For the most part, store.join is used for @storecache
54 For the most part, store.join is used for @storecache
55 decorators to invalidate caches when various files
55 decorators to invalidate caches when various files
56 change. We'll map the ones we care about, and ignore the rest.
56 change. We'll map the ones we care about, and ignore the rest.
57 """
57 """
58 if f in (b'00changelog.i', b'00manifest.i'):
58 if f in (b'00changelog.i', b'00manifest.i'):
59 # This is close enough: in order for the changelog cache
59 # This is close enough: in order for the changelog cache
60 # to be invalidated, HEAD will have to change.
60 # to be invalidated, HEAD will have to change.
61 return os.path.join(self.path, b'HEAD')
61 return os.path.join(self.path, b'HEAD')
62 elif f == b'lock':
62 elif f == b'lock':
63 # TODO: we probably want to map this to a git lock, I
63 # TODO: we probably want to map this to a git lock, I
64 # suspect index.lock. We should figure out what the
64 # suspect index.lock. We should figure out what the
65 # most-alike file is in git-land. For now we're risking
65 # most-alike file is in git-land. For now we're risking
66 # bad concurrency errors if another git client is used.
66 # bad concurrency errors if another git client is used.
67 return os.path.join(self.path, b'hgit-bogus-lock')
67 return os.path.join(self.path, b'hgit-bogus-lock')
68 elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
68 elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
69 return os.path.join(self.path, b'..', b'.hg', f)
69 return os.path.join(self.path, b'..', b'.hg', f)
70 raise NotImplementedError(b'Need to pick file for %s.' % f)
70 raise NotImplementedError(b'Need to pick file for %s.' % f)
71
71
72 def changelog(self, trypending):
72 def changelog(self, trypending):
73 # TODO we don't have a plan for trypending in hg's git support yet
73 # TODO we don't have a plan for trypending in hg's git support yet
74 return gitlog.changelog(self.git, self._db)
74 return gitlog.changelog(self.git, self._db)
75
75
76 def manifestlog(self, repo, storenarrowmatch):
76 def manifestlog(self, repo, storenarrowmatch):
77 # TODO handle storenarrowmatch and figure out if we need the repo arg
77 # TODO handle storenarrowmatch and figure out if we need the repo arg
78 return gitlog.manifestlog(self.git, self._db)
78 return gitlog.manifestlog(self.git, self._db)
79
79
80 def invalidatecaches(self):
80 def invalidatecaches(self):
81 pass
81 pass
82
82
83 def write(self, tr=None):
83 def write(self, tr=None):
84 # normally this handles things like fncache writes, which we don't have
84 # normally this handles things like fncache writes, which we don't have
85 pass
85 pass
86
86
87
87
88 def _makestore(orig, requirements, storebasepath, vfstype):
88 def _makestore(orig, requirements, storebasepath, vfstype):
89 if b'git' in requirements:
90 if not os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
91 raise error.Abort(
92 _(
93 b'repository specified git format in '
94 b'.hg/requires but has no .git directory'
95 )
96 )
89 # Check for presence of pygit2 only here. The assumption is that we'll
97 # Check for presence of pygit2 only here. The assumption is that we'll
90 # run this code iff we'll later need pygit2.
98 # run this code iff we'll later need pygit2.
91 if gitutil.get_pygit2() is None:
99 if gitutil.get_pygit2() is None:
92 raise error.Abort(
100 raise error.Abort(
93 _(
101 _(
94 b'the git extension requires the Python '
102 b'the git extension requires the Python '
95 b'pygit2 library to be installed'
103 b'pygit2 library to be installed'
96 )
104 )
97 )
105 )
98
106
99 if os.path.exists(
100 os.path.join(storebasepath, b'this-is-git')
101 ) and os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
102 return gitstore(storebasepath, vfstype)
107 return gitstore(storebasepath, vfstype)
103 return orig(requirements, storebasepath, vfstype)
108 return orig(requirements, storebasepath, vfstype)
104
109
105
110
106 class gitfilestorage(object):
111 class gitfilestorage(object):
107 def file(self, path):
112 def file(self, path):
108 if path[0:1] == b'/':
113 if path[0:1] == b'/':
109 path = path[1:]
114 path = path[1:]
110 return gitlog.filelog(self.store.git, self.store._db, path)
115 return gitlog.filelog(self.store.git, self.store._db, path)
111
116
112
117
113 def _makefilestorage(orig, requirements, features, **kwargs):
118 def _makefilestorage(orig, requirements, features, **kwargs):
114 store = kwargs['store']
119 store = kwargs['store']
115 if isinstance(store, gitstore):
120 if isinstance(store, gitstore):
116 return gitfilestorage
121 return gitfilestorage
117 return orig(requirements, features, **kwargs)
122 return orig(requirements, features, **kwargs)
118
123
119
124
120 def _setupdothg(ui, path):
125 def _setupdothg(ui, path):
121 dothg = os.path.join(path, b'.hg')
126 dothg = os.path.join(path, b'.hg')
122 if os.path.exists(dothg):
127 if os.path.exists(dothg):
123 ui.warn(_(b'git repo already initialized for hg\n'))
128 ui.warn(_(b'git repo already initialized for hg\n'))
124 else:
129 else:
125 os.mkdir(os.path.join(path, b'.hg'))
130 os.mkdir(os.path.join(path, b'.hg'))
126 # TODO is it ok to extend .git/info/exclude like this?
131 # TODO is it ok to extend .git/info/exclude like this?
127 with open(
132 with open(
128 os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
133 os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
129 ) as exclude:
134 ) as exclude:
130 exclude.write(b'\n.hg\n')
135 exclude.write(b'\n.hg\n')
131 with open(os.path.join(dothg, b'this-is-git'), 'wb') as f:
136 with open(os.path.join(dothg, b'requires'), 'wb') as f:
132 pass
133 with open(os.path.join(dothg, b'requirements'), 'wb') as f:
134 f.write(b'git\n')
137 f.write(b'git\n')
135
138
136
139
137 _BMS_PREFIX = 'refs/heads/'
140 _BMS_PREFIX = 'refs/heads/'
138
141
139
142
140 class gitbmstore(object):
143 class gitbmstore(object):
141 def __init__(self, gitrepo):
144 def __init__(self, gitrepo):
142 self.gitrepo = gitrepo
145 self.gitrepo = gitrepo
143
146
144 def __contains__(self, name):
147 def __contains__(self, name):
145 return (
148 return (
146 _BMS_PREFIX + pycompat.fsdecode(name)
149 _BMS_PREFIX + pycompat.fsdecode(name)
147 ) in self.gitrepo.references
150 ) in self.gitrepo.references
148
151
149 def __iter__(self):
152 def __iter__(self):
150 for r in self.gitrepo.listall_references():
153 for r in self.gitrepo.listall_references():
151 if r.startswith(_BMS_PREFIX):
154 if r.startswith(_BMS_PREFIX):
152 yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
155 yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
153
156
154 def __getitem__(self, k):
157 def __getitem__(self, k):
155 return (
158 return (
156 self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
159 self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
157 .peel()
160 .peel()
158 .id.raw
161 .id.raw
159 )
162 )
160
163
161 def get(self, k, default=None):
164 def get(self, k, default=None):
162 try:
165 try:
163 if k in self:
166 if k in self:
164 return self[k]
167 return self[k]
165 return default
168 return default
166 except gitutil.get_pygit2().InvalidSpecError:
169 except gitutil.get_pygit2().InvalidSpecError:
167 return default
170 return default
168
171
169 @property
172 @property
170 def active(self):
173 def active(self):
171 h = self.gitrepo.references['HEAD']
174 h = self.gitrepo.references['HEAD']
172 if not isinstance(h.target, str) or not h.target.startswith(
175 if not isinstance(h.target, str) or not h.target.startswith(
173 _BMS_PREFIX
176 _BMS_PREFIX
174 ):
177 ):
175 return None
178 return None
176 return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
179 return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
177
180
178 @active.setter
181 @active.setter
179 def active(self, mark):
182 def active(self, mark):
180 raise NotImplementedError
183 raise NotImplementedError
181
184
182 def names(self, node):
185 def names(self, node):
183 r = []
186 r = []
184 for ref in self.gitrepo.listall_references():
187 for ref in self.gitrepo.listall_references():
185 if not ref.startswith(_BMS_PREFIX):
188 if not ref.startswith(_BMS_PREFIX):
186 continue
189 continue
187 if self.gitrepo.references[ref].peel().id.raw != node:
190 if self.gitrepo.references[ref].peel().id.raw != node:
188 continue
191 continue
189 r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
192 r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
190 return r
193 return r
191
194
192 # Cleanup opportunity: this is *identical* to core's bookmarks store.
195 # Cleanup opportunity: this is *identical* to core's bookmarks store.
193 def expandname(self, bname):
196 def expandname(self, bname):
194 if bname == b'.':
197 if bname == b'.':
195 if self.active:
198 if self.active:
196 return self.active
199 return self.active
197 raise error.RepoLookupError(_(b"no active bookmark"))
200 raise error.RepoLookupError(_(b"no active bookmark"))
198 return bname
201 return bname
199
202
200 def applychanges(self, repo, tr, changes):
203 def applychanges(self, repo, tr, changes):
201 """Apply a list of changes to bookmarks
204 """Apply a list of changes to bookmarks
202 """
205 """
203 # TODO: this should respect transactions, but that's going to
206 # TODO: this should respect transactions, but that's going to
204 # require enlarging the gitbmstore to know how to do in-memory
207 # require enlarging the gitbmstore to know how to do in-memory
205 # temporary writes and read those back prior to transaction
208 # temporary writes and read those back prior to transaction
206 # finalization.
209 # finalization.
207 for name, node in changes:
210 for name, node in changes:
208 if node is None:
211 if node is None:
209 self.gitrepo.references.delete(
212 self.gitrepo.references.delete(
210 _BMS_PREFIX + pycompat.fsdecode(name)
213 _BMS_PREFIX + pycompat.fsdecode(name)
211 )
214 )
212 else:
215 else:
213 self.gitrepo.references.create(
216 self.gitrepo.references.create(
214 _BMS_PREFIX + pycompat.fsdecode(name),
217 _BMS_PREFIX + pycompat.fsdecode(name),
215 gitutil.togitnode(node),
218 gitutil.togitnode(node),
216 force=True,
219 force=True,
217 )
220 )
218
221
219
222
220 def init(orig, ui, dest=b'.', **opts):
223 def init(orig, ui, dest=b'.', **opts):
221 if opts.get('git', False):
224 if opts.get('git', False):
222 path = os.path.abspath(dest)
225 path = os.path.abspath(dest)
223 # TODO: walk up looking for the git repo
226 # TODO: walk up looking for the git repo
224 _setupdothg(ui, path)
227 _setupdothg(ui, path)
225 return 0
228 return 0
226 return orig(ui, dest=dest, **opts)
229 return orig(ui, dest=dest, **opts)
227
230
228
231
229 def reposetup(ui, repo):
232 def reposetup(ui, repo):
230 if isinstance(repo.store, gitstore):
233 if isinstance(repo.store, gitstore):
231 orig = repo.__class__
234 orig = repo.__class__
232 repo.store._progress_factory = repo.ui.makeprogress
235 repo.store._progress_factory = repo.ui.makeprogress
233
236
234 class gitlocalrepo(orig):
237 class gitlocalrepo(orig):
235 def _makedirstate(self):
238 def _makedirstate(self):
236 # TODO narrow support here
239 # TODO narrow support here
237 return dirstate.gitdirstate(
240 return dirstate.gitdirstate(
238 self.ui, self.vfs.base, self.store.git
241 self.ui, self.vfs.base, self.store.git
239 )
242 )
240
243
241 def commit(self, *args, **kwargs):
244 def commit(self, *args, **kwargs):
242 ret = orig.commit(self, *args, **kwargs)
245 ret = orig.commit(self, *args, **kwargs)
243 tid = self.store.git[gitutil.togitnode(ret)].tree.id
246 tid = self.store.git[gitutil.togitnode(ret)].tree.id
244 # DANGER! This will flush any writes staged to the
247 # DANGER! This will flush any writes staged to the
245 # index in Git, but we're sidestepping the index in a
248 # index in Git, but we're sidestepping the index in a
246 # way that confuses git when we commit. Alas.
249 # way that confuses git when we commit. Alas.
247 self.store.git.index.read_tree(tid)
250 self.store.git.index.read_tree(tid)
248 self.store.git.index.write()
251 self.store.git.index.write()
249 return ret
252 return ret
250
253
251 @property
254 @property
252 def _bookmarks(self):
255 def _bookmarks(self):
253 return gitbmstore(self.store.git)
256 return gitbmstore(self.store.git)
254
257
255 repo.__class__ = gitlocalrepo
258 repo.__class__ = gitlocalrepo
256 return repo
259 return repo
257
260
258
261
262 def _featuresetup(ui, supported):
263 # don't die on seeing a repo with the git requirement
264 supported |= {b'git'}
265
266
259 def extsetup(ui):
267 def extsetup(ui):
260 extensions.wrapfunction(localrepo, b'makestore', _makestore)
268 extensions.wrapfunction(localrepo, b'makestore', _makestore)
261 extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
269 extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
262 # Inject --git flag for `hg init`
270 # Inject --git flag for `hg init`
263 entry = extensions.wrapcommand(commands.table, b'init', init)
271 entry = extensions.wrapcommand(commands.table, b'init', init)
264 entry[1].extend(
272 entry[1].extend(
265 [(b'', b'git', None, b'setup up a git repository instead of hg')]
273 [(b'', b'git', None, b'setup up a git repository instead of hg')]
266 )
274 )
275 localrepo.featuresetupfuncs.add(_featuresetup)
@@ -1,230 +1,250
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
11 > count=10
10 > count=10
12 > gitcommit() {
11 > gitcommit() {
13 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000";
12 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000";
14 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
13 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
15 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
14 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
16 > count=`expr $count + 1`
15 > count=`expr $count + 1`
17 > }
16 > }
18
17
19 > echo "[extensions]" >> $HGRCPATH
18
19 Test auto-loading extension works:
20 $ mkdir nogit
21 $ cd nogit
22 $ mkdir .hg
23 $ echo git >> .hg/requires
24 $ hg status
25 abort: repository specified git format in .hg/requires but has no .git directory
26 [255]
27 $ git init
28 Initialized empty Git repository in $TESTTMP/nogit/.git/
29 This status invocation shows some hg gunk because we didn't use
30 `hg init --git`, which fixes up .git/info/exclude for us.
31 $ hg status
32 ? .hg/cache/git-commits.sqlite
33 ? .hg/cache/git-commits.sqlite-shm
34 ? .hg/cache/git-commits.sqlite-wal
35 ? .hg/requires
36 $ cd ..
37
38 Now globally enable extension for the rest of the test:
39 $ echo "[extensions]" >> $HGRCPATH
20 > echo "git=" >> $HGRCPATH
40 > echo "git=" >> $HGRCPATH
21
41
22 Make a new repo with git:
42 Make a new repo with git:
23 $ mkdir foo
43 $ mkdir foo
24 $ cd foo
44 $ cd foo
25 $ git init
45 $ git init
26 Initialized empty Git repository in $TESTTMP/foo/.git/
46 Initialized empty Git repository in $TESTTMP/foo/.git/
27 Ignore the .hg directory within git:
47 Ignore the .hg directory within git:
28 $ echo .hg >> .git/info/exclude
48 $ echo .hg >> .git/info/exclude
29 $ echo alpha > alpha
49 $ echo alpha > alpha
30 $ git add alpha
50 $ git add alpha
31 $ gitcommit -am 'Add alpha'
51 $ gitcommit -am 'Add alpha'
32 $ echo beta > beta
52 $ echo beta > beta
33 $ git add beta
53 $ git add beta
34 $ gitcommit -am 'Add beta'
54 $ gitcommit -am 'Add beta'
35 $ echo gamma > gamma
55 $ echo gamma > gamma
36 $ git status
56 $ git status
37 On branch master
57 On branch master
38 Untracked files:
58 Untracked files:
39 (use "git add <file>..." to include in what will be committed)
59 (use "git add <file>..." to include in what will be committed)
40 gamma
60 gamma
41
61
42 nothing added to commit but untracked files present (use "git add" to track)
62 nothing added to commit but untracked files present (use "git add" to track)
43
63
44 Without creating the .hg, hg status fails:
64 Without creating the .hg, hg status fails:
45 $ hg status
65 $ hg status
46 abort: no repository found in '$TESTTMP/foo' (.hg not found)!
66 abort: no repository found in '$TESTTMP/foo' (.hg not found)!
47 [255]
67 [255]
48 But if you run hg init --git, it works:
68 But if you run hg init --git, it works:
49 $ hg init --git
69 $ hg init --git
50 $ hg id --traceback
70 $ hg id --traceback
51 3d9be8deba43 tip master
71 3d9be8deba43 tip master
52 $ hg status
72 $ hg status
53 ? gamma
73 ? gamma
54 Log works too:
74 Log works too:
55 $ hg log
75 $ hg log
56 changeset: 1:3d9be8deba43
76 changeset: 1:3d9be8deba43
57 bookmark: master
77 bookmark: master
58 tag: tip
78 tag: tip
59 user: test <test@example.org>
79 user: test <test@example.org>
60 date: Mon Jan 01 00:00:11 2007 +0000
80 date: Mon Jan 01 00:00:11 2007 +0000
61 summary: Add beta
81 summary: Add beta
62
82
63 changeset: 0:c5864c9d16fb
83 changeset: 0:c5864c9d16fb
64 user: test <test@example.org>
84 user: test <test@example.org>
65 date: Mon Jan 01 00:00:10 2007 +0000
85 date: Mon Jan 01 00:00:10 2007 +0000
66 summary: Add alpha
86 summary: Add alpha
67
87
68
88
69
89
70 and bookmarks:
90 and bookmarks:
71 $ hg bookmarks
91 $ hg bookmarks
72 * master 1:3d9be8deba43
92 * master 1:3d9be8deba43
73
93
74 diff even works transparently in both systems:
94 diff even works transparently in both systems:
75 $ echo blah >> alpha
95 $ echo blah >> alpha
76 $ git diff
96 $ git diff
77 diff --git a/alpha b/alpha
97 diff --git a/alpha b/alpha
78 index 4a58007..faed1b7 100644
98 index 4a58007..faed1b7 100644
79 --- a/alpha
99 --- a/alpha
80 +++ b/alpha
100 +++ b/alpha
81 @@ -1* +1,2 @@ (glob)
101 @@ -1* +1,2 @@ (glob)
82 alpha
102 alpha
83 +blah
103 +blah
84 $ hg diff --git
104 $ hg diff --git
85 diff --git a/alpha b/alpha
105 diff --git a/alpha b/alpha
86 --- a/alpha
106 --- a/alpha
87 +++ b/alpha
107 +++ b/alpha
88 @@ -1,1 +1,2 @@
108 @@ -1,1 +1,2 @@
89 alpha
109 alpha
90 +blah
110 +blah
91
111
92 Remove a file, it shows as such:
112 Remove a file, it shows as such:
93 $ rm alpha
113 $ rm alpha
94 $ hg status
114 $ hg status
95 ! alpha
115 ! alpha
96 ? gamma
116 ? gamma
97
117
98 Revert works:
118 Revert works:
99 $ hg revert alpha --traceback
119 $ hg revert alpha --traceback
100 $ hg status
120 $ hg status
101 ? gamma
121 ? gamma
102 $ git status
122 $ git status
103 On branch master
123 On branch master
104 Untracked files:
124 Untracked files:
105 (use "git add <file>..." to include in what will be committed)
125 (use "git add <file>..." to include in what will be committed)
106 gamma
126 gamma
107
127
108 nothing added to commit but untracked files present (use "git add" to track)
128 nothing added to commit but untracked files present (use "git add" to track)
109
129
110 Add shows sanely in both:
130 Add shows sanely in both:
111 $ hg add gamma
131 $ hg add gamma
112 $ hg status
132 $ hg status
113 A gamma
133 A gamma
114 $ hg files
134 $ hg files
115 alpha
135 alpha
116 beta
136 beta
117 gamma
137 gamma
118 $ git ls-files
138 $ git ls-files
119 alpha
139 alpha
120 beta
140 beta
121 gamma
141 gamma
122 $ git status
142 $ git status
123 On branch master
143 On branch master
124 Changes to be committed:
144 Changes to be committed:
125 (use "git restore --staged <file>..." to unstage)
145 (use "git restore --staged <file>..." to unstage)
126 new file: gamma
146 new file: gamma
127
147
128
148
129 forget does what it should as well:
149 forget does what it should as well:
130 $ hg forget gamma
150 $ hg forget gamma
131 $ hg status
151 $ hg status
132 ? gamma
152 ? gamma
133 $ git status
153 $ git status
134 On branch master
154 On branch master
135 Untracked files:
155 Untracked files:
136 (use "git add <file>..." to include in what will be committed)
156 (use "git add <file>..." to include in what will be committed)
137 gamma
157 gamma
138
158
139 nothing added to commit but untracked files present (use "git add" to track)
159 nothing added to commit but untracked files present (use "git add" to track)
140
160
141 clean up untracked file
161 clean up untracked file
142 $ rm gamma
162 $ rm gamma
143
163
144 hg log FILE
164 hg log FILE
145
165
146 $ echo a >> alpha
166 $ echo a >> alpha
147 $ hg ci -m 'more alpha' --traceback --date '1583522787 18000'
167 $ hg ci -m 'more alpha' --traceback --date '1583522787 18000'
148 $ echo b >> beta
168 $ echo b >> beta
149 $ hg ci -m 'more beta'
169 $ hg ci -m 'more beta'
150 $ echo a >> alpha
170 $ echo a >> alpha
151 $ hg ci -m 'even more alpha'
171 $ hg ci -m 'even more alpha'
152 $ hg log -G alpha
172 $ hg log -G alpha
153 @ changeset: 4:6626247b7dc8
173 @ changeset: 4:6626247b7dc8
154 : bookmark: master
174 : bookmark: master
155 : tag: tip
175 : tag: tip
156 : user: test <test>
176 : user: test <test>
157 : date: Thu Jan 01 00:00:00 1970 +0000
177 : date: Thu Jan 01 00:00:00 1970 +0000
158 : summary: even more alpha
178 : summary: even more alpha
159 :
179 :
160 o changeset: 2:a1983dd7fb19
180 o changeset: 2:a1983dd7fb19
161 : user: test <test>
181 : user: test <test>
162 : date: Fri Mar 06 14:26:27 2020 -0500
182 : date: Fri Mar 06 14:26:27 2020 -0500
163 : summary: more alpha
183 : summary: more alpha
164 :
184 :
165 o changeset: 0:c5864c9d16fb
185 o changeset: 0:c5864c9d16fb
166 user: test <test@example.org>
186 user: test <test@example.org>
167 date: Mon Jan 01 00:00:10 2007 +0000
187 date: Mon Jan 01 00:00:10 2007 +0000
168 summary: Add alpha
188 summary: Add alpha
169
189
170 $ hg log -G beta
190 $ hg log -G beta
171 o changeset: 3:d8ee22687733
191 o changeset: 3:d8ee22687733
172 : user: test <test>
192 : user: test <test>
173 : date: Thu Jan 01 00:00:00 1970 +0000
193 : date: Thu Jan 01 00:00:00 1970 +0000
174 : summary: more beta
194 : summary: more beta
175 :
195 :
176 o changeset: 1:3d9be8deba43
196 o changeset: 1:3d9be8deba43
177 | user: test <test@example.org>
197 | user: test <test@example.org>
178 ~ date: Mon Jan 01 00:00:11 2007 +0000
198 ~ date: Mon Jan 01 00:00:11 2007 +0000
179 summary: Add beta
199 summary: Add beta
180
200
181
201
182 hg annotate
202 hg annotate
183
203
184 $ hg annotate alpha
204 $ hg annotate alpha
185 0: alpha
205 0: alpha
186 2: a
206 2: a
187 4: a
207 4: a
188 $ hg annotate beta
208 $ hg annotate beta
189 1: beta
209 1: beta
190 3: b
210 3: b
191
211
192
212
193 Files in subdirectories. TODO: case-folding support, make this `A`
213 Files in subdirectories. TODO: case-folding support, make this `A`
194 instead of `a`.
214 instead of `a`.
195
215
196 $ mkdir a
216 $ mkdir a
197 $ echo "This is file mu." > a/mu
217 $ echo "This is file mu." > a/mu
198 $ hg ci -A -m 'Introduce file a/mu'
218 $ hg ci -A -m 'Introduce file a/mu'
199 adding a/mu
219 adding a/mu
200
220
201 Both hg and git agree a/mu is part of the repo
221 Both hg and git agree a/mu is part of the repo
202
222
203 $ git ls-files
223 $ git ls-files
204 a/mu
224 a/mu
205 alpha
225 alpha
206 beta
226 beta
207 $ hg files
227 $ hg files
208 a/mu
228 a/mu
209 alpha
229 alpha
210 beta
230 beta
211
231
212 hg and git status both clean
232 hg and git status both clean
213
233
214 $ git status
234 $ git status
215 On branch master
235 On branch master
216 nothing to commit, working tree clean
236 nothing to commit, working tree clean
217 $ hg status
237 $ hg status
218
238
219
239
220 node|shortest works correctly
240 node|shortest works correctly
221 $ hg log -T '{node}\n' | sort
241 $ hg log -T '{node}\n' | sort
222 3d9be8deba43482be2c81a4cb4be1f10d85fa8bc
242 3d9be8deba43482be2c81a4cb4be1f10d85fa8bc
223 6626247b7dc8f231b183b8a4761c89139baca2ad
243 6626247b7dc8f231b183b8a4761c89139baca2ad
224 a1983dd7fb19cbd83ad5a1c2fc8bf3d775dea12f
244 a1983dd7fb19cbd83ad5a1c2fc8bf3d775dea12f
225 ae1ab744f95bfd5b07cf573baef98a778058537b
245 ae1ab744f95bfd5b07cf573baef98a778058537b
226 c5864c9d16fb3431fe2c175ff84dc6accdbb2c18
246 c5864c9d16fb3431fe2c175ff84dc6accdbb2c18
227 d8ee22687733a1991813560b15128cd9734f4b48
247 d8ee22687733a1991813560b15128cd9734f4b48
228 $ hg log -r ae1ab744f95bfd5b07cf573baef98a778058537b --template "{shortest(node,1)}\n"
248 $ hg log -r ae1ab744f95bfd5b07cf573baef98a778058537b --template "{shortest(node,1)}\n"
229 ae
249 ae
230
250
General Comments 0
You need to be logged in to leave comments. Login now