chainsaw-update: taking care of initial cloning
authorGeorges Racinet <georges.racinet@octobus.net>
Fri, 23 Feb 2024 11:41:55 +0100
changeset 51433 d36a81d70f25
parent 51432 e79c7320605f
child 51434 dd519ea71416
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.
hgext/chainsaw.py
tests/test-chainsaw-update.t
--- 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