chainsaw-update: taking care of initial cloning
Perhaps we should go just a bit lower level than this `instance()`,
since the main added value in our use-case is full path resolution,
that we need to do anyway for the rmtree cleanup.
--- a/hgext/chainsaw.py Fri Feb 23 11:30:58 2024 +0100
+++ b/hgext/chainsaw.py Fri Feb 23 11:41:55 2024 +0100
@@ -26,8 +26,12 @@
cmdutil,
commands,
error,
+ localrepo,
registrar,
)
+from mercurial.utils import (
+ urlutil,
+)
cmdtable = {}
command = registrar.command(cmdtable)
@@ -73,23 +77,49 @@
b'',
_(b'repository to clone from'),
),
+ (
+ b'',
+ b'dest',
+ b'',
+ _(b'repository to update to REV (possibly cloning)'),
+ ),
+ (
+ b'',
+ b'initial-clone-minimal',
+ False,
+ _(
+ b'Pull only the prescribed revision upon initial cloning. '
+ b'This has the side effect of ignoring clone-bundles, '
+ b'which if often slower on the client side and stressful '
+ b'to the server than applying available clone bundles.'
+ ),
+ ),
],
- _(b'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE...'),
+ _(
+ b'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE --dest DEST'
+ ),
helpbasic=True,
+ norepo=True,
)
-def update(ui, repo, **opts):
+def update(ui, **opts):
"""pull and update to a given revision, no matter what, (EXPERIMENTAL)
Context of application: *some* Continuous Integration (CI) systems,
packaging or deployment tools.
- Wanted end result: clean working directory updated at the given revision.
+ Wanted end result: local repository at the given REPO_PATH, having the
+ latest changes to the given revision and with a clean working directory
+ updated at the given revision.
chainsaw-update pulls from one source, then updates the working directory
to the given revision, overcoming anything that would stand in the way.
By default, it will:
+ - clone if the local repo does not exist yet, **removing any directory
+ at the given path** that would not be a Mercurial repository.
+ The initial clone is full by default, so that clonebundles can be
+ applied. Use the --initial-clone-minimal flag to avoid this.
- break locks if needed, leading to possible corruption if there
is a concurrent write access.
- perform recovery actions if needed
@@ -116,10 +146,36 @@
"""
rev = opts['rev']
source = opts['source']
+ repo_path = opts['dest']
if not rev:
raise error.InputError(_(b'specify a target revision with --rev'))
if not source:
raise error.InputError(_(b'specify a pull path with --source'))
+ if not repo_path:
+ raise error.InputError(_(b'specify a repo path with --dest'))
+ repo_path = urlutil.urllocalpath(repo_path)
+
+ try:
+ repo = localrepo.instance(ui, repo_path, create=False)
+ repo_created = False
+ ui.status(_(b'loaded repository at "%s"\n' % repo_path))
+ except error.RepoError:
+ try:
+ shutil.rmtree(repo_path)
+ except FileNotFoundError:
+ ui.status(_(b'no such directory: "%s"\n' % repo_path))
+ else:
+ ui.status(
+ _(
+ b'removed non-repository file or directory '
+ b'at "%s"' % repo_path
+ )
+ )
+
+ ui.status(_(b'creating repository at "%s"\n' % repo_path))
+ repo = localrepo.instance(ui, repo_path, create=True)
+ repo_created = True
+
if repo.svfs.tryunlink(b'lock'):
ui.status(_(b'had to break store lock\n'))
if repo.vfs.tryunlink(b'wlock'):
@@ -129,10 +185,14 @@
repo.recover()
ui.status(_(b'pulling from %s\n') % source)
+ if repo_created and not opts.get('initial_clone_minimal'):
+ pull_revs = []
+ else:
+ pull_revs = [rev]
overrides = {(b'ui', b'quiet'): True}
- with ui.configoverride(overrides, b'chainsaw-update'):
+ with repo.ui.configoverride(overrides, b'chainsaw-update'):
pull = cmdutil.findcmd(b'pull', commands.table)[1][0]
- pull(ui, repo, source, rev=[rev], remote_hidden=False)
+ pull(repo.ui, repo, source, rev=pull_revs, remote_hidden=False)
purge = cmdutil.findcmd(b'purge', commands.table)[1][0]
purge(
--- a/tests/test-chainsaw-update.t Fri Feb 23 11:30:58 2024 +0100
+++ b/tests/test-chainsaw-update.t Fri Feb 23 11:41:55 2024 +0100
@@ -58,26 +58,40 @@
Actual tests
============
-Simple invocation
------------------
+Initial cloning if needed
+-------------------------
- $ hg init repo
- $ cd repo
- $ hg admin::chainsaw-update --rev default --source ../src
+ $ hg admin::chainsaw-update --dest repo --rev default --source ./src
+ no such directory: "repo"
+ creating repository at "repo"
recovering after interrupted transaction, if any
no interrupted transaction available
- pulling from ../src
+ pulling from ./src
updating to revision 'default'
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
chainsaw-update to revision 'default' for repository at '$TESTTMP/repo' done
+ $ cd repo
$ hg log -G
- @ changeset: 1:bfcb8e629987
+ @ changeset: 3:bfcb8e629987
| tag: tip
+ | parent: 0:06f48e4098b8
| user: test
| date: Thu Jan 01 00:00:00 1970 +0000
| summary: B_0
|
+ | o changeset: 2:7fd8de258aa4
+ | | branch: A
+ | | user: test
+ | | date: Thu Jan 01 00:00:00 1970 +0000
+ | | summary: A_1
+ | |
+ | o changeset: 1:ae1692b8aadb
+ |/ branch: A
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: A_0
+ |
o changeset: 0:06f48e4098b8
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -103,7 +117,8 @@
wlock: (.*?), process 171814, host invalid.host.test/effffffc \((\d+)s\) (re)
[2]
- $ hg admin::chainsaw-update --no-purge-ignored --rev default --source ../src
+ $ hg admin::chainsaw-update --no-purge-ignored --dest . --rev default --source ../src
+ loaded repository at "."
had to break store lock
had to break working copy lock
recovering after interrupted transaction, if any
@@ -127,9 +142,39 @@
C root
$ echo 2 > ../src/foo
- $ hg -R ../src commit -m2
- $ hg admin::chainsaw-update --rev default --source ../src -q
+ $ hg -R ../src commit -mB_1
+ $ hg admin::chainsaw-update --dest . --rev default --source ../src -q
no interrupted transaction available
+ $ hg log -G
+ @ changeset: 4:973ab81c95fb
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: B_1
+ |
+ o changeset: 3:bfcb8e629987
+ | parent: 0:06f48e4098b8
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: B_0
+ |
+ | o changeset: 2:7fd8de258aa4
+ | | branch: A
+ | | user: test
+ | | date: Thu Jan 01 00:00:00 1970 +0000
+ | | summary: A_1
+ | |
+ | o changeset: 1:ae1692b8aadb
+ |/ branch: A
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: A_0
+ |
+ o changeset: 0:06f48e4098b8
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: R_0
+
$ hg status -A
C foo
C root
@@ -148,7 +193,7 @@
C foo
C root
- $ hg admin::chainsaw-update --no-purge-ignored --rev default --source ../src -q
+ $ hg admin::chainsaw-update --no-purge-ignored --dest . --rev default --source ../src -q
no interrupted transaction available
$ hg status --all
I bar
@@ -158,7 +203,7 @@
$ cat bar
ignored
- $ hg admin::chainsaw-update --rev default --source ../src -q
+ $ hg admin::chainsaw-update --dest . --rev default --source ../src -q
no interrupted transaction available
$ hg status --all
C .hgignore
@@ -167,3 +212,44 @@
$ test -f bar
[1]
+test --minimal-initial-cloning variant
+--------------------------------------
+
+With `--minimal-initial-cloning`, there is no "requesting all changes"
+message. Hence clone bundles would be bypassed (TODO test both cases
+# with an actual clone-bundle)
+
+ $ cd ..
+ $ hg admin::chainsaw-update --dest repo2 --rev default --source src --initial-clone-minimal
+ no such directory: "repo2"
+ creating repository at "repo2"
+ recovering after interrupted transaction, if any
+ no interrupted transaction available
+ pulling from src
+ updating to revision 'default'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ chainsaw-update to revision 'default' for repository at '$TESTTMP/repo2' done
+
+ $ cd repo2
+ $ hg log -G
+ @ changeset: 2:973ab81c95fb
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: B_1
+ |
+ o changeset: 1:bfcb8e629987
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: B_0
+ |
+ o changeset: 0:06f48e4098b8
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: R_0
+
+ $ hg status -A
+ C foo
+ C root
+ $ cat foo
+ 2