diff hgext/chainsaw.py @ 51433:d36a81d70f25

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.
author Georges Racinet <georges.racinet@octobus.net>
date Fri, 23 Feb 2024 11:41:55 +0100
parents fe68a2dc0bf2
children dd519ea71416
line wrap: on
line diff
--- 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(