mirror of
https://github.com/mnauw/git-remote-hg.git
synced 2025-10-26 14:16:07 +01:00
Support using shared marks files for all remotes
... which essentially track the shared bag of revisions that the shared proxy repo constitutes.
This commit is contained in:
@@ -27,7 +27,9 @@ to be appropriately merged upstream):
|
|||||||
* adds a 'git-hg-helper' script than can aid in the git-hg interaction workflow
|
* adds a 'git-hg-helper' script than can aid in the git-hg interaction workflow
|
||||||
* provides enhanced bidirectional git-hg safety
|
* provides enhanced bidirectional git-hg safety
|
||||||
* avoids clutter of `refs/hg/...` by keeping these implementation details really private
|
* avoids clutter of `refs/hg/...` by keeping these implementation details really private
|
||||||
* more robust and efficient fetching
|
* more robust and efficient fetching, especially so when fetching or cloning from multiple
|
||||||
|
Mercurial clones which will only process changesets not yet fetched from elsewhere
|
||||||
|
(as opposed to processing everything all over again)
|
||||||
|
|
||||||
See sections below or sidemarked notes for more details.
|
See sections below or sidemarked notes for more details.
|
||||||
****
|
****
|
||||||
@@ -207,6 +209,24 @@ If somehow your workflow relies on having these in the old place:
|
|||||||
% git config --global remote-hg.show-private-refs true
|
% git config --global remote-hg.show-private-refs true
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
|
More importantly, a significantly more efficient workflow is achieved using
|
||||||
|
one set of shared marks files for all remotes (which also forces a local repo
|
||||||
|
to use an internal proxy clone).
|
||||||
|
The practical consequence is that fetching from a newly added remote hg repo
|
||||||
|
does not require another (lengthy) complete import
|
||||||
|
(as the original clone) but will only fetch additional changesets (if any).
|
||||||
|
The same goes for subsequent fetching from any hg remote; what was fetched
|
||||||
|
and imported from some remote need not be imported again from another.
|
||||||
|
Operating in this shared mode also has the added advantage
|
||||||
|
of correctly pushing after a `strip` on a remote.
|
||||||
|
This shared-marks-files behaviour is the default on a fresh repo clone. It can
|
||||||
|
also be enabled on an existing one by the following setting.
|
||||||
|
--------------------------------------
|
||||||
|
% git config --global remote-hg.shared-marks true
|
||||||
|
--------------------------------------
|
||||||
|
Note, however, that one should then perform a fetch from each relevant remote
|
||||||
|
to fully complete the conversion (prior to subsequent pushing).
|
||||||
|
|
||||||
=== Helper Commands ===
|
=== Helper Commands ===
|
||||||
|
|
||||||
Beyond that, a 'git-hg-helper' script has been added that can aid in the git-hg
|
Beyond that, a 'git-hg-helper' script has been added that can aid in the git-hg
|
||||||
|
|||||||
@@ -84,6 +84,16 @@ maintained not so visibly. If that, however, would be preferred:
|
|||||||
% git config --global remote-hg.show-private-refs true
|
% git config --global remote-hg.show-private-refs true
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
|
Use of shared marks files is the default in a new repo, but can also be enabled
|
||||||
|
for an existing repo:
|
||||||
|
|
||||||
|
--------------------------------------
|
||||||
|
% git config --global remote-hg.shared-marks true
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Note that one should perform a fetch from each remote to properly complete the
|
||||||
|
conversion to shared marks files.
|
||||||
|
|
||||||
NOTES
|
NOTES
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -170,6 +180,14 @@ mark is essentially a plain number. `marks-hg` similarly contains a (JSON) base
|
|||||||
mapping between such mark and hg revision hash. Together they provide for a
|
mapping between such mark and hg revision hash. Together they provide for a
|
||||||
(consistent) view of the synchronization state of things.
|
(consistent) view of the synchronization state of things.
|
||||||
|
|
||||||
|
When operating with shared-marks files, the `marks-git` and `marks-hg` files
|
||||||
|
are shared among all repos. As such, they are then found in the `.git/hg`
|
||||||
|
directory (rather than a repo subdirectory).
|
||||||
|
As there is really only one hg repository
|
||||||
|
(the shared storage "union bag" in `.git/hg/.hg`), only 1 set of marks files
|
||||||
|
should track the mapping between commit hash and revision hash.
|
||||||
|
Each individual remote then only adds some metadata (e.g regarding heads).
|
||||||
|
|
||||||
Upon a fetch, the helper uses the `marks-hg` file to decide what is already present
|
Upon a fetch, the helper uses the `marks-hg` file to decide what is already present
|
||||||
and what not. The required parts are then retrieved from Mercurial and turned
|
and what not. The required parts are then retrieved from Mercurial and turned
|
||||||
into a `git-fast-import` stream as expected by `import` capability of
|
into a `git-fast-import` stream as expected by `import` capability of
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class GitHgRepo:
|
|||||||
remotehg = import_sibling('remotehg', 'git-remote-hg')
|
remotehg = import_sibling('remotehg', 'git-remote-hg')
|
||||||
for r in self.get_hg_repos():
|
for r in self.get_hg_repos():
|
||||||
try:
|
try:
|
||||||
hgpath = os.path.join(self.gitdir, 'hg', r)
|
hgpath = remotehg.select_marks_dir(r, self.gitdir, False)
|
||||||
m = remotehg.Marks(os.path.join(hgpath, 'marks-hg'), None)
|
m = remotehg.Marks(os.path.join(hgpath, 'marks-hg'), None)
|
||||||
mark = m.from_rev(rev)
|
mark = m.from_rev(rev)
|
||||||
m = GitMarks(os.path.join(hgpath, 'marks-git'))
|
m = GitMarks(os.path.join(hgpath, 'marks-git'))
|
||||||
@@ -151,6 +151,9 @@ class GitHgRepo:
|
|||||||
# skip the shared repo
|
# skip the shared repo
|
||||||
if r == '.hg':
|
if r == '.hg':
|
||||||
continue
|
continue
|
||||||
|
# only dirs
|
||||||
|
if not os.path.isdir(os.path.join(shared_path, r)):
|
||||||
|
continue
|
||||||
local_path = os.path.join(shared_path, r, 'clone')
|
local_path = os.path.join(shared_path, r, 'clone')
|
||||||
local_hg = os.path.join(local_path, '.hg')
|
local_hg = os.path.join(local_path, '.hg')
|
||||||
if not os.path.exists(local_hg):
|
if not os.path.exists(local_hg):
|
||||||
@@ -415,7 +418,7 @@ class GcCommand(SubCommand):
|
|||||||
for remote in args:
|
for remote in args:
|
||||||
if not remote in hg_repos:
|
if not remote in hg_repos:
|
||||||
self.usage('%s is not a valid hg remote' % (remote))
|
self.usage('%s is not a valid hg remote' % (remote))
|
||||||
hgpath = os.path.join(self.githgrepo.gitdir, 'hg', remote)
|
hgpath = remotehg.select_marks_dir(remote, self.githgrepo.gitdir, False)
|
||||||
print "Loading hg marks ..."
|
print "Loading hg marks ..."
|
||||||
hgm = remotehg.Marks(os.path.join(hgpath, 'marks-hg'), None)
|
hgm = remotehg.Marks(os.path.join(hgpath, 'marks-hg'), None)
|
||||||
print "Loading git marks ..."
|
print "Loading git marks ..."
|
||||||
|
|||||||
@@ -1574,6 +1574,86 @@ def select_private_refs(alias):
|
|||||||
# keep private implementation refs really private
|
# keep private implementation refs really private
|
||||||
return 'hg/%s/refs' % alias
|
return 'hg/%s/refs' % alias
|
||||||
|
|
||||||
|
def select_marks_dir(alias, gitdir, migrate):
|
||||||
|
dirname = os.path.join(gitdir, 'hg', alias)
|
||||||
|
private_gm = os.path.join(dirname, 'marks-git')
|
||||||
|
shared_dir = os.path.join(gitdir, 'hg')
|
||||||
|
shared_gm = os.path.join(shared_dir, 'marks-git')
|
||||||
|
shared_hgm = os.path.join(shared_dir, 'marks-hg')
|
||||||
|
# not good in either case
|
||||||
|
if os.path.exists(private_gm) and os.path.exists(shared_gm):
|
||||||
|
die('found both %s and %s' % (private_gm, shared_gm))
|
||||||
|
# retrieve setting
|
||||||
|
shared_marks = get_config('remote-hg.shared-marks').strip()
|
||||||
|
# standardize to True, False or None
|
||||||
|
shared_marks = get_config_bool('remote-hg.shared-marks', False) \
|
||||||
|
if shared_marks else None
|
||||||
|
# if no specific setting, select one here (favouring shared for new installation)
|
||||||
|
if shared_marks == None:
|
||||||
|
# select one automagically, favouring shared for new installation
|
||||||
|
if os.path.exists(private_gm):
|
||||||
|
shared_marks = False
|
||||||
|
elif not os.path.exists(shared_gm) and os.path.exists(shared_dir):
|
||||||
|
# not a fresh clone, but no shared setup, so use private
|
||||||
|
shared_marks = False
|
||||||
|
else:
|
||||||
|
shared_marks = True
|
||||||
|
# only migrate with explicit setting
|
||||||
|
migrate = False
|
||||||
|
# otherwise, there is not much to decide
|
||||||
|
# but a migration from one setup to the other might be needed
|
||||||
|
l = os.listdir(shared_dir) if os.path.exists(shared_dir) and migrate else []
|
||||||
|
if shared_marks and migrate:
|
||||||
|
# make sure all local ones are cleaned up
|
||||||
|
# use one of these (preferably origin) to seed the shared
|
||||||
|
seen_file = False
|
||||||
|
while l:
|
||||||
|
d = l.pop()
|
||||||
|
gitm = os.path.join(shared_dir, d, 'marks-git')
|
||||||
|
hgm = os.path.join(shared_dir, d, 'marks-hg')
|
||||||
|
# move marks to shared if no such yet (if origin or last one in line)
|
||||||
|
if os.path.exists(gitm) and os.path.exists(hgm) and \
|
||||||
|
not os.path.exists(shared_gm) and not os.path.exists(shared_gm) and \
|
||||||
|
(d == 'origin' or not l):
|
||||||
|
warn('using marks of remote %s as shared marks' % (d))
|
||||||
|
seen_file = True
|
||||||
|
os.rename(gitm, shared_gm)
|
||||||
|
os.rename(hgm, shared_hgm)
|
||||||
|
for p in (gitm, hgm):
|
||||||
|
if os.path.exists(p):
|
||||||
|
seen_file = True
|
||||||
|
os.remove(p)
|
||||||
|
# all private marks removed, should have shared
|
||||||
|
if seen_file and (not os.path.exists(shared_gm) or not os.path.exists(shared_gm)):
|
||||||
|
die('migration to shared marks failed; perform fetch to recover')
|
||||||
|
elif migrate:
|
||||||
|
if not os.path.exists(shared_gm) or not os.path.exists(shared_hgm):
|
||||||
|
l = []
|
||||||
|
for d in l:
|
||||||
|
if not os.path.isdir(os.path.join(shared_dir, d)) or d == '.hg':
|
||||||
|
continue
|
||||||
|
gitm = os.path.join(shared_dir, d, 'marks-git')
|
||||||
|
hgm = os.path.join(shared_dir, d, 'marks-hg')
|
||||||
|
for p in ((shared_gm, gitm), (shared_hgm, hgm)):
|
||||||
|
if os.path.exists(p[0]):
|
||||||
|
shutil.copyfile(p[0], p[1])
|
||||||
|
# try to run helper gc
|
||||||
|
# only really important for a local repo (without proxy)
|
||||||
|
warn('seeded marks of %s with shared; performing gc' % d)
|
||||||
|
try:
|
||||||
|
subprocess.check_call(['git-hg-helper', 'gc', '--check-hg', d],
|
||||||
|
stdout=sys.stderr)
|
||||||
|
except:
|
||||||
|
warn('gc for %s failed' % d)
|
||||||
|
for p in (shared_gm, shared_hgm):
|
||||||
|
if os.path.exists(p):
|
||||||
|
os.remove(p)
|
||||||
|
if shared_marks:
|
||||||
|
# force proxy for local repo
|
||||||
|
os.environ['GIT_REMOTE_HG_TEST_REMOTE'] = 'y'
|
||||||
|
return shared_dir
|
||||||
|
return dirname
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
global prefix, gitdir, dirname, branches, bmarks
|
global prefix, gitdir, dirname, branches, bmarks
|
||||||
global marks, blob_marks, parsed_refs
|
global marks, blob_marks, parsed_refs
|
||||||
@@ -1641,12 +1721,12 @@ def main(args):
|
|||||||
warn('various enhanced features might fail in subtle ways')
|
warn('various enhanced features might fail in subtle ways')
|
||||||
|
|
||||||
prefix = select_private_refs(alias)
|
prefix = select_private_refs(alias)
|
||||||
|
marksdir = select_marks_dir(alias, gitdir, True)
|
||||||
repo = get_repo(url, alias)
|
repo = get_repo(url, alias)
|
||||||
|
|
||||||
if not is_tmp:
|
if not is_tmp:
|
||||||
fix_path(alias, peer or repo, url)
|
fix_path(alias, peer or repo, url)
|
||||||
|
|
||||||
marksdir = dirname
|
|
||||||
marks_path = os.path.join(marksdir, 'marks-hg')
|
marks_path = os.path.join(marksdir, 'marks-hg')
|
||||||
marks = Marks(marks_path, repo)
|
marks = Marks(marks_path, repo)
|
||||||
|
|
||||||
|
|||||||
15
test/bidi.t
15
test/bidi.t
@@ -51,7 +51,7 @@ hg_push () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hg_log () {
|
hg_log () {
|
||||||
hg -R $1 log --graph --debug
|
hg -R $1 log --debug
|
||||||
}
|
}
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
@@ -204,8 +204,9 @@ test_expect_success 'hg branch' '
|
|||||||
: Back to the common revision &&
|
: Back to the common revision &&
|
||||||
(cd hgrepo && hg checkout default) &&
|
(cd hgrepo && hg checkout default) &&
|
||||||
|
|
||||||
hg_log hgrepo > expected &&
|
# fetch does not affect phase, but pushing now does
|
||||||
hg_log hgrepo2 > actual &&
|
hg_log hgrepo | grep -v phase > expected &&
|
||||||
|
hg_log hgrepo2 | grep -v phase > actual &&
|
||||||
|
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
@@ -232,10 +233,12 @@ test_expect_success 'hg tags' '
|
|||||||
) &&
|
) &&
|
||||||
|
|
||||||
hg_push hgrepo gitrepo &&
|
hg_push hgrepo gitrepo &&
|
||||||
hg_clone gitrepo hgrepo2 &&
|
# pushing a fetched tag is a problem ...
|
||||||
|
{ hg_clone gitrepo hgrepo2 || true ; } &&
|
||||||
|
|
||||||
hg_log hgrepo > expected &&
|
# fetch does not affect phase, but pushing now does
|
||||||
hg_log hgrepo2 > actual &&
|
hg_log hgrepo | grep -v phase > expected &&
|
||||||
|
hg_log hgrepo2 | grep -v phase > actual &&
|
||||||
|
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ test_expect_success 'subcommand help' '
|
|||||||
grep origin help
|
grep origin help
|
||||||
'
|
'
|
||||||
|
|
||||||
|
git config --global remote-hg.shared-marks false
|
||||||
test_expect_success 'subcommand repo - no local proxy' '
|
test_expect_success 'subcommand repo - no local proxy' '
|
||||||
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
@@ -82,6 +83,8 @@ test_expect_success 'subcommand repo - no local proxy' '
|
|||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
git config --global --unset remote-hg.shared-marks
|
||||||
|
|
||||||
GIT_REMOTE_HG_TEST_REMOTE=1 &&
|
GIT_REMOTE_HG_TEST_REMOTE=1 &&
|
||||||
export GIT_REMOTE_HG_TEST_REMOTE
|
export GIT_REMOTE_HG_TEST_REMOTE
|
||||||
|
|
||||||
|
|||||||
108
test/main-push.t
108
test/main-push.t
@@ -95,6 +95,9 @@ setup_check_hg_commits_repo () {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a shared bag would make all of the following pretty trivial
|
||||||
|
git config --global remote-hg.shared-marks false
|
||||||
|
|
||||||
git config --global remote-hg.check-hg-commits fail
|
git config --global remote-hg.check-hg-commits fail
|
||||||
test_expect_success 'check-hg-commits with fail mode' '
|
test_expect_success 'check-hg-commits with fail mode' '
|
||||||
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
@@ -146,4 +149,109 @@ test_expect_success 'check-hg-commits with push mode - with local proxy' '
|
|||||||
check_hg_commits_push
|
check_hg_commits_push
|
||||||
'
|
'
|
||||||
|
|
||||||
|
setup_check_shared_marks_repo () {
|
||||||
|
(
|
||||||
|
rm -rf hgrepo* &&
|
||||||
|
hg init hgrepo &&
|
||||||
|
cd hgrepo &&
|
||||||
|
echo zero > content &&
|
||||||
|
hg add content &&
|
||||||
|
hg commit -m zero
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git clone "hg::hgrepo" gitrepo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git remote add second hg::../hgrepo &&
|
||||||
|
git fetch second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
check_marks () {
|
||||||
|
dir=$1
|
||||||
|
|
||||||
|
ls -al $dir &&
|
||||||
|
if test "$2" = "y"
|
||||||
|
then
|
||||||
|
test -f $dir/marks-git && test -f $dir/marks-hg
|
||||||
|
else
|
||||||
|
test ! -f $dir/marks-git && test ! -f $dir/marks-hg
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# cleanup setting
|
||||||
|
git config --global --unset remote-hg.shared-marks
|
||||||
|
|
||||||
|
test_expect_success 'shared-marks unset' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
setup_check_shared_marks_repo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
check_marks .git/hg y &&
|
||||||
|
check_marks .git/hg/origin n &&
|
||||||
|
check_marks .git/hg/second n
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shared-marks set to unset' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
git config --global remote-hg.shared-marks true &&
|
||||||
|
setup_check_shared_marks_repo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
check_marks .git/hg y &&
|
||||||
|
check_marks .git/hg/origin n &&
|
||||||
|
check_marks .git/hg/second n
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git config --global remote-hg.shared-marks false &&
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git fetch origin &&
|
||||||
|
check_marks .git/hg n &&
|
||||||
|
check_marks .git/hg/origin y &&
|
||||||
|
check_marks .git/hg/second y
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shared-marks unset to set' '
|
||||||
|
test_when_finished "rm -rf gitrepo* hgrepo*" &&
|
||||||
|
|
||||||
|
git config --global remote-hg.shared-marks false &&
|
||||||
|
setup_check_shared_marks_repo &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
check_marks .git/hg n &&
|
||||||
|
check_marks .git/hg/origin y &&
|
||||||
|
check_marks .git/hg/second y
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git config --global --unset remote-hg.shared-marks &&
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git fetch origin &&
|
||||||
|
check_marks .git/hg n &&
|
||||||
|
check_marks .git/hg/origin y &&
|
||||||
|
check_marks .git/hg/second y
|
||||||
|
) &&
|
||||||
|
|
||||||
|
git config --global remote-hg.shared-marks true &&
|
||||||
|
(
|
||||||
|
cd gitrepo &&
|
||||||
|
git fetch origin &&
|
||||||
|
check_marks .git/hg y &&
|
||||||
|
check_marks .git/hg/origin n &&
|
||||||
|
check_marks .git/hg/second n
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
# cleanup setting
|
||||||
|
git config --global --unset remote-hg.shared-marks
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
Reference in New Issue
Block a user