diff --git a/hgext/chainsaw.py b/hgext/chainsaw.py --- a/hgext/chainsaw.py +++ b/hgext/chainsaw.py @@ -26,8 +26,12 @@ from mercurial import ( cmdutil, commands, error, + localrepo, registrar, ) +from mercurial.utils import ( + urlutil, +) cmdtable = {} command = registrar.command(cmdtable) @@ -73,23 +77,49 @@ testedwith = b'ships-with-hg-core' 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 @@ def update(ui, repo, **opts): """ 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 @@ def update(ui, repo, **opts): 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( diff --git a/tests/test-chainsaw-update.t b/tests/test-chainsaw-update.t --- a/tests/test-chainsaw-update.t +++ b/tests/test-chainsaw-update.t @@ -58,26 +58,40 @@ setup 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 @@ from the current hostname (happens a lot 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 @@ purging. 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 @@ the --no-purge-ignored flag is passed, b 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 @@ the --no-purge-ignored flag is passed, b $ 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 @@ the --no-purge-ignored flag is passed, b $ 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