Upstreaming the Scalar command
Subsystems (2)
cover Upstreaming the Scalar command
tl;dr: This series contributes the Scalar command to the Git project. This command provides an opinionated way to create and configure repositories with a focus on very large repositories.
Background
==========
Years ago, Microsoft wanted to move the source code of the Windows operating system to Git. The challenge there was to prove that Git could scale to massive monorepos. The VFS for Git (formerly GVFS) project was born to take up that challenge.
The final solution included a virtual filesystem (with both user-mode and kernel components) and a customized fork of Git for Windows. This solution contained several key concepts, such as only populating a portion of the working directory, demand-fetching blobs, and performing periodic repo maintenance in the background. However, the required kernel drivers made it difficult to port the solution to other platforms.
But it was realized that many of these key concepts were independent of the actual VFS and its projection of the working directory. The Scalar project was created to make that separation, refine the key concepts, and then extract those features into the new Scalar command.
The present
===========
The Scalar project provides a completely functional non-virtual experience for monorepos. But why stop there. The Scalar project was designed to be a self-destructing vehicle to allow those key concepts to be moved into core Git itself for the benefit of all. For example, partial clone, sparse-checkout, and background maintenance have already been upstreamed and removed from Scalar proper. This patch series provides a C-based implementation of the final remaining portions of the Scalar command. This will make it easier for users to experiment with the Scalar command. It will also make it substantially easier to experiment with moving functionality from Scalar into core Git, while maintaining backwards-compatibility for existing Scalar users.
The C-based Scalar has been shipped to Scalar users, and can be tested by any interested reader:
https://github.com/microsoft/git/releases/tag/v2.33.0.vfs.0.0 (it offers a Git for Windows installer, a macOS package and an Ubuntu package).
Opportunities
=============
Apart from providing the Scalar command, this contribution is intended to serve as a basis for further mailing list discussions on moving (some of) these key concepts into the main Git commands.
For example, we previously discussed the idea of a "git big-clone" that does much of what "scalar clone" is doing. This patch series is a step to make such functionality exist in the Git code base while we simmer on what such a "git big-clone" command-line interface would look like.
This is one of many possible ways to do this. Creating a 'git big-clone' could lock Git into backwards compatibility concerns so it is necessary to approach such an endeavor with caution. As a discussion starter, the scalar clone <url> command does roughly this:
1. git clone --sparse --filter=blob:none /src
2. git -C /src sparse-checkout init --cone
3. git -C /src config (many times)
4. git -C /src maintenance start
It is my hope inspire discussions about what parts of Scalar could go into core Git, and where, and in which form. While we wish to maintain backwards-compatibility of Scalar's command-line interface (because it is already in use), by having the Scalar code in the same code base as Git's, it will be much easier to move functionality without having to maintain loose version coupling between independently-versioned Scalar and Git. The tight version-coupling, along with having access to libgit.a also allows the C-based implementation of Scalar to be much smaller than the original .NET version.
For example, we might choose in the future to implement, say, git clone --scale=partial,cone to initialize a partial clone with a cone-sparse checkout, that would not only be totally doable, and not only would we already have precedent and data to prove that this actually makes engineers happy who have to work on ginormous repositories, but we could then also implement it by moving parts of contrib/scalar/ to builtin/ (where contrib/scalar/ would then call the built-ins accordingly rather than hard-coding the defaults itself).
We now also have the opportunity to discuss the merits of Scalar's clone caching, which is not actually part of this patch series because it is a bit coupled with the GVFS parts of microsoft/git for the moment, where clones automatically get registered with a populated alternate repository that is identified by the URL, meaning: subsequent clones of the same repository are vastly faster than the first one because they do not actually download the already-received objects again, they access the cache instead.
Another thing that I could imagine to be discussed at length is the distinction between enlistment and worktree (where the latter is the actual Git worktree and usually lives in the src/ subdirectory of the former). This encourages untracked and ignored files to be placed outside the worktree, making Git's job much easier. This idea, too, might find its way in one way or another into Git proper.
These are just a few concepts in Scalar that do not yet have equivalents in Git. By putting this initial implementation into contrib/, we create a foundation for future discussions of these concepts.
We plan on updating the recommended config settings in scalar register as new Git features are available (such as builtin FSMonitor and sparse-index, when ready). To facilitate upgrading existing Scalar enlistments, their paths are automatically added to the [scalar] section of the global Git config, and the scalar reconfigure --all command will process all of them.
Epilogue
========
Now, to address some questions that I imagine every reader has who made it this far:
* Why not put the Scalar functionality directly into a built-in? Creating a Git builtin requires scrutiny over every aspect of the feature, which is difficult to do while also maintaining the command-line interface contract and expected behavior of the Scalar command (there are existing users, after all). By having the Scalar command in contrib/, we present a simple option for users to have these features in the short term while the Git contributor community decides which bits to absorb into Git built-ins.
* Why implement the Scalar command in the Git codebase? We ported Scalar to the microsoft/git fork for several reasons. First, we realized it was possible now that the core features exist inside Git itself. Second, compiling Scalar directly within a version of Git allows us to remove a version compatibility check from each config option that might or might not apply based on the installed Git version. Finally, this new location has greatly simplified our release process and the installation process for users. We now have ways to install Scalar with microsoft/git via winget, brew, and apt-get. This has been the case since we shipped v2.32.0 to our users, read: this setup has served us well already.
* Why contribute Scalar to the Git project? We are biased, of course, yet we do have evidence that the Scalar command is a helpful tool that offers an simple way to handle huge repositories with ease. By contributing it to the core Git project, we are able to share it with more users, especially some users who do not want to install the microsoft/git fork. We intend to include Scalar as a component in git-for-windows/git, but are contributing it here first. Further, we think there is benefit to the Git developer community as this presents an example of how to set certain defaults that work for large repositories.
* Does this integrate with the built-in FSMonitor yet? No, not yet. I do have a couple of add-on patch series lined up, one of them being the integration with the built-in FSMonitor, which obviously has to wait until the FSMonitor patch series advances further.
Derrick Stolee (4):
scalar: 'register' sets recommended config and starts maintenance
scalar: 'unregister' stops background maintenance
scalar: implement 'scalar list'
scalar: implement the run command
Johannes Schindelin (10):
scalar: create a rudimentary executable
scalar: start documenting the command
scalar: create test infrastructure
scalar: let 'unregister' handle a deleted enlistment directory
gracefully
scalar: implement the clone subcommand
scalar: teach 'clone' to support the --single-branch option
scalar: allow reconfiguring an existing enlistment
scalar: teach 'reconfigure' to optionally handle all registered
enlistments
scalar: implement the version command
scalar: accept -C and -c options before the subcommand
Matthew John Cheetham (1):
scalar: implement the delete command
Makefile | 8 + contrib/scalar/.gitignore | 5 + contrib/scalar/Makefile | 57 +++ contrib/scalar/scalar.c | 844 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 152 ++++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 ++++ 7 files changed, 1232 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c create mode 100644 contrib/scalar/scalar.txt create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
base-commit: ebf3c04b262aa27fbb97f8a0156c2347fecafafb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1005%2Fdscho%2Fscalar-the-beginning-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Signature · 1 line
gitgitgadget
PATCH 1/15 scalar: create a rudimentary executable
From: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
With this patch, we start the journey from that C# project to move what is left to Git's own contrib/ directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It was always to plan to contribute all of the proven strategies back to core Git.
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
With partial clone, sparse checkout and git maintenance having been upstreamed, there is little left that scalar.exe does that which git.exe cannot do. One such thing is that scalar clone <url> will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the scalar executable and put it into the contrib/scalar/ subdirectory.
The slightly awkward addition of the contrib/scalar/* bits to the top-level Makefile are actually really required: we want to link to libgit.a, which means that we will need to use the very same CFLAGS and LDFLAGS as the rest of Git.
An early development version of this patch tried to replicate all the conditional code in contrib/scalar/Makefile (e.g. NO_POLL) just like contrib/svn-fe/Makefile used to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disabling compat/poll/, compat/regex/, compat/win32mmap.c & friends depending on the current platform... To put it mildly: it was a major mess.
Instead, this patch makes minimal changes to the top-level Makefile so that the bits in contrib/scalar/ can be compiled and linked, and adds a contrib/scalar/Makefile that uses the top-level Makefile in a most minimal way to do the actual compiling.
Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +8 -0
@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
contrib/scalar/.gitignore new file +2 -0
@@ -0,0 +1,2 @@ +/*.exe +/scalar
contrib/scalar/Makefile new file +34 -0
@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCE
contrib/scalar/scalar.c new file +36 -0
@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +}
Signature · 1 line
gitgitgadget
PATCH 2/15 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This commit establishes the infrastructure to build the manual page for the scalar command.
--- contrib/scalar/.gitignore | 3 +++ contrib/scalar/Makefile | 14 +++++++++++++- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 contrib/scalar/scalar.txt
contrib/scalar/.gitignore +3 -0
@@ -1,2 +1,5 @@ +/*.xml +/*.1 +/*.html /*.exe /scalar
contrib/scalar/Makefile +13 -1
@@ -6,6 +6,7 @@ ifndef V QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir + QUIET = @ else export V endif
@@ -30,5 +31,16 @@ $(TARGETS): $(GITLIBS) scalar.c clean: $(RM) $(TARGETS) + $(RM) scalar.1 scalar.html scalar.xml -.PHONY: all clean FORCE +docs: scalar.html scalar.1 + +scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel + +scalar.html scalar.1: scalar.txt + $(QUIET_SUBDIR0)../../Documentation$(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/scalar/scalar.txt \ + ../contrib/scalar/$@ + $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . + +.PHONY: all clean docs FORCE
contrib/scalar/scalar.txt new file +38 -0
@@ -0,0 +1,38 @@ +scalar(1) +========= + +NAME +---- +scalar - an opinionated repository management tool + +SYNOPSIS +-------- +[verse] +scalar <command> [<options>] + +DESCRIPTION +----------- + +Scalar is an opinionated repository management tool. By creating new +repositories or registering existing repositories with Scalar, your Git +experience will speed up. Scalar sets advanced Git config settings, +maintains your repositories in the background, and helps reduce data sent +across the network. + +An important Scalar concept is the enlistment: this is the top-level directory +of the project. It usually contains the subdirectory `src/` which is a Git +worktree. This encourages the separation between tracked files (inside `src/`) +and untracked files, such as build artifacts (outside `src/`). When registering +an existing Git worktree with Scalar whose name is not `src`, the enlistment +will be identical to the worktree. + +The `scalar` command implements various subcommands, and different options +depending on the subcommand. + +SEE ALSO +-------- +linkgit:git-maintenance[1]. + +Scalar +--- +Associated with the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
PATCH 3/15 scalar: create test infrastructure
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
To test the Scalar command, create a test script in contrib/scalar/t that is executed as make -C contrib/scalar test. Since Scalar has no meaningful capabilities yet, the only test is rather simple. We will add more tests in subsequent commits that introduce corresponding, new functionality.
Note: this test script is intended to test scalar only lightly, even after all of the functionality is implemented.
A more comprehensive functional (or: integration) test suite can be found at https://github.com/microsoft/scalar; It is used in the workflow
https://github.com/microsoft/git/blob/HEAD/.github/workflows/scalar-functional-tests.yml
in Microsoft's Git fork. This test suite performs end-to-end tests with a real remote repository, and is run as part of the regular CI builds. Since those tests require some functionality supported only by Microsoft's Git fork ("GVFS protocol"), there is no intention to port that fuller test suite to contrib/scalar/.
--- contrib/scalar/Makefile | 17 +++++-- contrib/scalar/t/Makefile | 78 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 17 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
contrib/scalar/Makefile +14 -3
@@ -3,6 +3,7 @@ QUIET_SUBDIR1 = ifneq ($(findstring s,$(MAKEFLAGS)),s) ifndef V + QUIET_GEN = @echo ' ' GEN $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -21,7 +22,7 @@ include ../../config.mak.uname TARGETS = scalar$(X) scalar.o GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a -all: scalar$X +all: scalar$X ../../bin-wrappers/scalar $(GITLIBS): $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ -30,9 +31,19 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar $(RM) scalar.1 scalar.html scalar.xml +../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile + @mkdir -p ../../bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell cd ../.. && pwd)|' \ + -e 's|@@PROG@@|contrib/scalar/scalar$(X)|' < $< > $@ && \ + chmod +x $@ + +test: all + $(MAKE) -C t + docs: scalar.html scalar.1 scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
@@ -43,4 +54,4 @@ scalar.html scalar.1: scalar.txt ../contrib/scalar/$@ $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . -.PHONY: all clean docs FORCE +.PHONY: all clean docs test FORCE
contrib/scalar/t/Makefile new file +78 -0
78 lines not on this page · raw message
contrib/scalar/t/t9099-scalar.sh new file +17 -0
@@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='test the `scalar` command' + +TEST_DIRECTORY=$PWD/../../../t +export TEST_DIRECTORY + +# Make it work with --no-bin-wrappers +PATH=$PWD/..:$PATH + +. ../../../t/test-lib.sh + +test_expect_success 'scalar shows a usage' ' + test_expect_code 129 scalar -h +' + +test_done
Signature · 1 line
gitgitgadget
PATCH 4/15 scalar: 'register' sets recommended config and starts maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Let's start implementing the register command. With this commit, recommended settings are configured upon scalar register, and Git's background maintenance is started.
The recommended config settings may very well change in the future. For example, once the built-in FSMonitor is available, we will want to enable it upon scalar register. For that reason, we explicitly support running scalar register in an already-registered enlistment.
--- contrib/scalar/scalar.c | 256 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 14 ++- 2 files changed, 269 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +256 -0
@@ -5,11 +5,267 @@#include "cache.h" #include "gettext.h" #include "parse-options.h" +#include "config.h" +#include "run-command.h" + +/* +* Remove the deepest subdirectory in the provided path string. Path must not +* include a trailing path separator. Returns 1 if parent directory found, +* otherwise 0. +*/ +static int strbuf_parent_directory (struct strbuf *buf) +{ +size_t len = buf->len; +size_t offset =offset_1st_component (buf->buf); +char *path_sep =find_last_dir_sep (buf->buf + offset); +strbuf_setlen (buf, path_sep ? path_sep - buf->buf : offset); + +return buf->len < len; +} + +static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); +do { +const size_t len = path.len; + +/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + } + +/* reset to original path */ +strbuf_setlen (&path, len); + +/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) { +strbuf_setlen (&path, len); + +if (enlistment_root) { +/* +* If the worktree's directory's name is `src`, the enlistment is the +* parent directory, otherwise it is identical to the worktree. +*/ + root =strip_path_suffix (path.buf,"src" ); +strbuf_addstr (enlistment_root, root ? root : path.buf); +free (root); + } + + enlistment_found =1 ; +break ; + } + +strbuf_setlen (&path, len); + }while (strbuf_parent_directory (&path)); + +if (!enlistment_found) +die (_ ("could not find enlistment root" )); + +if (chdir (path.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), path.buf); + +strbuf_release (&path); +setup_git_directory (); +} + +static int run_git (const char *arg, ...) +{ +struct strvec argv =STRVEC_INIT ; +va_list args; +const char *p; +int res; + +va_start (args, arg); +strvec_push (&argv, arg); + while ((p = va_arg(args, const char *))) +strvec_push (&argv, p); +va_end (args); + + res =run_command_v_opt (argv.v,RUN_GIT_CMD ); + +strvec_clear (&argv); +return res; +} + +static int set_recommended_config (void ) +{ +struct { +const char *key; +const char *value; + } config[] = { + {"am.keepCR" ,"true" }, + {"core.FSCache" ,"true" }, + {"core.multiPackIndex" ,"true" }, + {"core.preloadIndex" ,"true" }, +#ifndef WIN32 + {"core.untrackedCache" ,"true" }, +#else +/* +* Unfortunately, Scalar's Functional Tests demonstrated +* that the untracked cache feature is unreliable on Windows +* (which is a bummer because that platform would benefit the +* most from it). For some reason, freshly created files seem +* not to update the directory's `lastModified` time +* immediately, but the untracked cache would need to rely on +* that. +* +* Therefore, with a sad heart, we disable this very useful +* feature on Windows. +*/ + {"core.untrackedCache" ,"false" }, +#endif + {"core.bare" ,"false" }, + {"core.logAllRefUpdates" ,"true" }, + {"credential.https://dev.azure.com.useHttpPath" ,"true" }, + {"credential.validate" ,"false" },/* GCM4W-only */ + {"gc.auto" ,"0" }, + {"gui.GCWarning" ,"false" }, + {"index.threads" ,"true" }, + {"index.version" ,"4" }, + {"merge.stat" ,"false" }, + {"merge.renames" ,"false" }, + {"pack.useBitmaps" ,"false" }, + {"pack.useSparse" ,"true" }, + {"receive.autoGC" ,"false" }, + {"reset.quiet" ,"true" }, + {"feature.manyFiles" ,"false" }, + {"feature.experimental" ,"false" }, + {"fetch.unpackLimit" ,"1" }, + {"fetch.writeCommitGraph" ,"false" }, +#ifdef WIN32 + {"http.sslBackend" ,"schannel" }, +#endif + {"status.aheadBehind" ,"false" }, + {"commitGraph.generationVersion" ,"1" }, + {"core.autoCRLF" ,"false" }, + {"core.safeCRLF" ,"false" }, + {NULL ,NULL }, + }; +int i; +char *value; + +for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + } + } + +/* +* The `log.excludeDecoration` setting is special because it allows +* for multiple values. +*/ +if (git_config_get_string ("log.excludeDecoration" , &value)) { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"created" ); +if (git_config_set_multivar_gently ("log.excludeDecoration" , +"refs/prefetch/*" , +CONFIG_REGEX_NONE ,0 )) +return error (_ ("could not configure " +"log.excludeDecoration" )); + }else { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"exists" ); +free (value); + } + +return 0 ; +} + +static int start_maintenance (void ) +{ +return run_git ("maintenance" ,"start" ,NULL ); +} + +static int add_enlistment (void ) +{ +int res; + +if (!the_repository->worktree) +die (_ ("Scalar enlistments require a worktree" )); + + res =run_git ("config" ,"--global" ,"--get" ,"--fixed-value" , +"scalar.repo" , the_repository->worktree,NULL ); + +/* +* If the setting is already there, then do nothing. +*/ +if (!res) +return 0 ; + +return run_git ("config" ,"--global" ,"--add" , +"scalar.repo" , the_repository->worktree,NULL ); +} + +static int register_dir (void ) +{ +int res =add_enlistment (); + +if (!res) + res =set_recommended_config (); + +if (!res) + res =start_maintenance (); + +return res; +} + +static int cmd_register (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar register [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return register_dir (); +}static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + {"register" , cmd_register }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +13 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar <command> [<options>] +scalar register [<enlistment>] DESCRIPTION -----------
@@ -29,6 +29,18 @@ will be identical to the worktree. The `scalar` command implements various subcommands, and different options depending on the subcommand. +COMMANDS +-------- + +Register +~~~~~~~~ + +register [<enlistment>]:: + Adds the enlistment's repository to the list of registered repositories + and starts background maintenance. If `<enlistment>` is not provided, + then the enlistment associated with the current working directory is + registered. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH 5/15 scalar: 'unregister' stops background maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Just like scalar register starts the scheduled background maintenance, scalar unregister stops it. Note that we use git maintenance start in scalar register, but we do not use git maintenance stop in scalar unregister: this would stop maintenance for _all_ repositories, not just for the one we want to unregister.
The unregister command also removes the corresponding entry from the [scalar] section in the global Git config.
--- contrib/scalar/scalar.c | 50 ++++++++++++++++++++++++++++++++------- contrib/scalar/scalar.txt | 8 +++++++ 2 files changed, 50 insertions(+), 8 deletions(-)
contrib/scalar/scalar.c +42 -8
@@ -205,12 +205,12 @@ static int set_recommended_config(void)return 0 ; } -static int start_maintenance (void ) +static int toggle_maintenance (int enable) { -return run_git ("maintenance" ,"start" ,NULL ); +return run_git ("maintenance" , enable ?"start" :"unregister" ,NULL ); } -static int add_enlistment (void ) +static int add_or_remove_enlistment (int add) {int res;
@@ -221,24 +221,39 @@ static int add_enlistment(void) "scalar.repo", the_repository->worktree, NULL);/* -* If the setting is already there, then do nothing. +* If we want to add and the setting is already there, then do nothing. +* If we want to remove and the setting is not there, then do nothing. */ -if (!res) +if ((add && !res) || (!add && res))return 0 ; -return run_git ("config" ,"--global" ,"--add" , +return run_git ("config" ,"--global" , add ?"--add" :"--unset" , + add ?"--no-fixed-value" :"--fixed-value" ,"scalar.repo" , the_repository->worktree,NULL ); }static int register_dir (void ) { -int res =add_enlistment (); +int res =add_or_remove_enlistment (1 );if (!res) res =set_recommended_config ();if (!res) - res =start_maintenance (); + res =toggle_maintenance (1 ); + +return res; +} + +static int unregister_dir (void ) +{ +int res =0 ; + +if (toggle_maintenance (0 ) <0 ) + res =-1 ; + +if (add_or_remove_enlistment (0 ) <0 ) + res =-1 ;return res; }
@@ -261,11 +276,30 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_unregister (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar unregister [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return unregister_dir (); +} +static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { {"register" , cmd_register }, + {"unregister" , cmd_unregister }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +8 -0
@@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] scalar register [<enlistment>] +scalar unregister [<enlistment>] DESCRIPTION -----------
@@ -41,6 +42,13 @@ register [<enlistment>]:: then the enlistment associated with the current working directory is registered. +Unregister +~~~~~~~~~~ + +unregister [<enlistment>]:: + Remove the specified repository from the list of repositories + registered with Scalar and stop the scheduled background maintenance. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH 7/15 scalar: implement 'scalar list'
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The produced list simply consists of those repositories registered under the multi-valued scalar.repo config setting in the user's Git config.
--- contrib/scalar/scalar.c | 11 +++++++++++ contrib/scalar/scalar.txt | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +11 -0
@@ -258,6 +258,16 @@ static int unregister_dir(void)return res; } +static int cmd_list (int argc,const char **argv) +{ +if (argc !=1 ) +die (_ ("`scalar list` does not take arguments" )); + +if (run_git ("config" ,"--global" ,"--get-all" ,"scalar.repo" ,NULL ) <0 ) +return -1 ; +return 0 ; +} +static int cmd_register (int argc,const char **argv) {struct option options[] = {
@@ -344,6 +354,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, { NULL, NULL},
contrib/scalar/scalar.txt +11 -1
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -28,11 +29,20 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. +depending on the subcommand. With the exception of `list`, all subcommands +expect to be run in an enlistment. COMMANDS -------- +List +~~~~ + +list:: + To see which repositories are currently registered by the service, run + `scalar list`. This subcommand does not need to be run inside a Scalar + enlistment. + Register ~~~~~~~~
Signature · 1 line
gitgitgadget
PATCH 6/15 scalar: let 'unregister' handle a deleted enlistment directory gracefully
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
When a user deleted an enlistment manually, let's be generous and _still_ unregister it.
--- contrib/scalar/scalar.c | 46 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 15 +++++++++++ 2 files changed, 61 insertions(+)
contrib/scalar/scalar.c +46 -0
@@ -276,6 +276,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int remove_deleted_enlistment (struct strbuf *path) +{ +int res =0 ; +strbuf_realpath_forgiving (path, path->buf,1 ); + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"scalar.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"maintenance.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +return res; +} +static int cmd_unregister (int argc,const char **argv) {struct option options[] = {
@@ -289,6 +307,34 @@ static int cmd_unregister(int argc, const char **argv) argc =parse_options (argc, argv,NULL , options, usage,0 ); +/* +* Be forgiving when the enlistment or worktree does not even exist any +* longer; This can be the case if a user deleted the worktree by +* mistake and _still_ wants to unregister the thing. +*/ +if (argc ==1 ) { +struct strbuf src_path =STRBUF_INIT , workdir_path =STRBUF_INIT ; + +strbuf_addf (&src_path,"%s/src/.git" , argv[0 ]); +strbuf_addf (&workdir_path,"%s/.git" , argv[0 ]); +if (!is_directory (src_path.buf) && !is_directory (workdir_path.buf)) { +/* remove possible matching registrations */ +int res =-1 ; + +strbuf_strip_suffix (&src_path,"/.git" ); + res =remove_deleted_enlistment (&src_path) && res; + +strbuf_strip_suffix (&workdir_path,"/.git" ); + res =remove_deleted_enlistment (&workdir_path) && res; + +strbuf_release (&src_path); +strbuf_release (&workdir_path); +return res; + } +strbuf_release (&src_path); +strbuf_release (&workdir_path); + } +setup_enlistment_directory (argc, argv, usage, options,NULL );return unregister_dir ();
contrib/scalar/t/t9099-scalar.sh +15 -0
@@ -14,4 +14,19 @@ test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h ' +test_expect_success 'scalar unregister' ' + git init vanish/src && + scalar register vanish/src && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/vanish/src" scalar.repos && + rm -rf vanish/src/.git && + scalar unregister vanish && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/vanish/src" scalar.repos +' + test_done
Signature · 1 line
gitgitgadget
PATCH 8/15 scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated clone command: it tries to use a partial clone and sets up a sparse checkout by default. In contrast to git clone, scalar clone sets up the worktree in the src/ subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Also, it registers the repository for regular, scheduled maintenance, and configures a flurry of configuration settings based on the experience and experiments of the Microsoft Windows and the Microsoft Office development teams.
Note: We intentionally use a slightly wasteful set_config() function (which does not reuse a single strbuf, for example, though performance _really_ does not matter here) for convenience and readability.
Also note: since the scalar clone command is by far the most commonly called scalar subcommand, we document it at the top of the manual page.
--- contrib/scalar/scalar.c | 200 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 35 +++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 262 insertions(+), 5 deletions(-)
contrib/scalar/scalar.c +200 -0
@@ -7,6 +7,7 @@#include "parse-options.h" #include "config.h" #include "run-command.h" +#include "refs.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -258,6 +259,204 @@ static int unregister_dir(void)return res; } +/* printf-style interface, expects `<key>=<value>` argument */ +static int set_config (const char *fmt, ...) +{ +struct strbuf buf =STRBUF_INIT ; +char *value; +int res; +va_list args; + +va_start (args, fmt); +strbuf_vaddf (&buf, fmt, args); +va_end (args); + + value =strchr (buf.buf,'=' ); +if (value) + *(value++) ='\0' ; + res =git_config_set_gently (buf.buf, value); +strbuf_release (&buf); + +return res; +} + +static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +strbuf_addstr (&out,"-\n" ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +char *ref = out.buf; + +while ((ref =strstr (ref +1 ,"\nref: " ))) { +const char *p; +char *head, *branch; + + ref +=strlen ("\nref: " ); + head =strstr (ref,"\tHEAD" ); + +if (!head ||memchr (ref,'\n' , head - ref)) +continue ; + +if (skip_prefix (ref,"refs/heads/" , &p)) { + branch =xstrndup (p, head - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(head - ref), ref); +strbuf_release (&out); +return NULL ; + } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} + +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b); + } + +if ((res =run_git ("-c" , buf.buf,"init" ,"--" , dir,NULL ))) + goto cleanup; + +if (chdir (dir) <0 ) { + res =error_errno (_ ("could not switch to '%s'" ), dir); + goto cleanup; + } + +setup_git_directory (); + +/* common-main already logs `argv` */ +trace2_def_repo (the_repository); + +if (!branch && !(branch =remote_default_branch (url))) { + res =error (_ ("failed to get default branch for '%s'" ), url); + goto cleanup; + } + +if (set_config ("remote.origin.url=%s" , url) || +set_config ("remote.origin.fetch=" +"+refs/heads/*:refs/remotes/origin/*" ) || +set_config ("remote.origin.promisor=true" ) || +set_config ("remote.origin.partialCloneFilter=blob:none" )) { + res =error (_ ("could not configure remote in '%s'" ), dir); + goto cleanup; + } + +if (!full_clone && + (res =run_git ("sparse-checkout" ,"init" ,"--cone" ,NULL ))) + goto cleanup; + +if (set_recommended_config ()) +return error (_ ("could not configure '%s'" ), dir); + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) { +warning (_ ("partial clone failed; attempting full clone" )); + +if (set_config ("remote.origin.promisor" ) || +set_config ("remote.origin.partialCloneFilter" )) { + res =error (_ ("could not configure for full clone" )); + goto cleanup; + } + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) + goto cleanup; + } + +if ((res =set_config ("branch.%s.remote=origin" , branch))) + goto cleanup; +if ((res =set_config ("branch.%s.merge=refs/heads/%s" , + branch, branch))) + goto cleanup; + +strbuf_reset (&buf); +strbuf_addf (&buf,"origin/%s" , branch); + res =run_git ("checkout" ,"-f" ,"-t" , buf.buf,NULL ); +if (res) + goto cleanup; + + res =register_dir (); + +cleanup: +free (enlistment); +free (dir); +strbuf_release (&buf); +return res; +} +static int cmd_list (int argc,const char **argv) {if (argc !=1 )
@@ -354,6 +553,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "clone", cmd_clone }, { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister },
contrib/scalar/scalar.txt +30 -5
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -29,19 +30,43 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `list`, all subcommands -expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone` and `list`, all +subcommands expect to be run in an enlistment. COMMANDS -------- +Clone +~~~~~ + +clone [<options>] <url> [<enlistment>]:: + Clones the specified repository, similar to linkgit:git-clone[1]. By + default, only commit and tree objects are cloned. Once finished, the + worktree is located at `<enlistment>/src`. ++ +The sparse-checkout feature is enabled (except when run with `--full-clone`) +and the only files present are those in the top-level directory. Use +`git sparse-checkout set` to expand the set of directories you want to see, +or `git sparse-checkout disable` to expand to all files (see +linkgit:git-sparse-checkout[1] for more details). You can explore the +subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. + +-b <name>:: +--branch <name>:: + Instead of checking out the branch pointed to by the cloned repository's + HEAD, check out the `<name>` branch instead. + +--[no-]full-clone:: + A sparse-checkout is initialized by default. This behavior can be turned + off via `--full-clone`. + List ~~~~ list:: To see which repositories are currently registered by the service, run - `scalar list`. This subcommand does not need to be run inside a Scalar - enlistment. + `scalar list`. This subcommand, like `clone`, does not need to be run + inside a Scalar enlistment. Register ~~~~~~~~
@@ -61,7 +86,7 @@ unregister [<enlistment>]:: SEE ALSO -------- -linkgit:git-maintenance[1]. +linkgit:git-clone[1], linkgit:git-maintenance[1]. Scalar ---
contrib/scalar/t/t9099-scalar.sh +32 -0
@@ -10,6 +10,9 @@ PATH=$PWD/..:$PATH . ../../../t/test-lib.sh +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt" +export GIT_TEST_MAINT_SCHEDULER + test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h '
@@ -29,4 +32,33 @@ test_expect_success 'scalar unregister' ' ! grep -F "$(pwd)/vanish/src" scalar.repos ' +test_expect_success 'set up repository to clone' ' + test_commit first && + test_commit second && + test_commit third && + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + git config uploadPack.allowFilter true && + git config uploadPack.allowAnySHA1InWant true +' + +test_expect_success 'scalar clone' ' + second=$(git rev-parse --verify second:second.t) && + scalar clone "file://$(pwd)" cloned && + ( + cd cloned/src && + + git config --get --global --fixed-value maintenance.repo \ + "$(pwd)" && + + test_path_is_missing 1/2 && + test_must_fail git rev-list --missing=print $second && + git rev-list $second && + git cat-file blob $second >actual && + echo "second" >expect && + test_cmp expect actual + ) +' + test_done
Signature · 1 line
gitgitgadget
PATCH 9/15 scalar: teach 'clone' to support the --single-branch option
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like git clone, the scalar clone command now also offers to restrict the clone to a single branch.
--- contrib/scalar/scalar.c | 9 +++++++-- contrib/scalar/scalar.txt | 12 +++++++++++- contrib/scalar/t/t9099-scalar.sh | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-)
contrib/scalar/scalar.c +7 -2
@@ -333,12 +333,15 @@ static char *remote_default_branch(const char *url)static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ; -int full_clone =0 ; +int full_clone =0 , single_branch =0 ;struct option clone_options[] = {OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ),N_ ("branch to checkout after clone" )),OPT_BOOL (0 ,"full-clone" , &full_clone,N_ ("when cloning, create full working directory" )), +OPT_BOOL (0 ,"single-branch" , &single_branch, +N_ ("only download metadata for the branch that will " +"be checked out" )),OPT_END (), };const char *const clone_usage[] = {
@@ -409,7 +412,9 @@ static int cmd_clone(int argc, const char **argv)if (set_config ("remote.origin.url=%s" , url) ||set_config ("remote.origin.fetch=" -"+refs/heads/*:refs/remotes/origin/*" ) || +"+refs/heads/%s:refs/remotes/origin/%s" , + single_branch ? branch :"*" , + single_branch ? branch :"*" ) ||set_config ("remote.origin.promisor=true" ) ||set_config ("remote.origin.partialCloneFilter=blob:none" )) { res =error (_ ("could not configure remote in '%s'" ), dir);
contrib/scalar/scalar.txt +11 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] +scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -56,6 +56,16 @@ subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. Instead of checking out the branch pointed to by the cloned repository's HEAD, check out the `<name>` branch instead. +--[no-]single-branch:: + Clone only the history leading to the tip of a single branch, + either specified by the `--branch` option or the primary + branch remote's `HEAD` points at. ++ +Further fetches into the resulting repository will only update the +remote-tracking branch for the branch this option was used for the initial +cloning. If the HEAD at the remote did not point at any branch when +`--single-branch` clone was made, no remote-tracking branch is created. + --[no-]full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`.
contrib/scalar/t/t9099-scalar.sh +5 -1
@@ -45,13 +45,17 @@ test_expect_success 'set up repository to clone' ' test_expect_success 'scalar clone' ' second=$(git rev-parse --verify second:second.t) && - scalar clone "file://$(pwd)" cloned && + scalar clone "file://$(pwd)" cloned --single-branch && ( cd cloned/src && git config --get --global --fixed-value maintenance.repo \ "$(pwd)" && + git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual && + echo "refs/remotes/origin/parallel" >expect && + test_cmp expect actual && + test_path_is_missing 1/2 && test_must_fail git rev-list --missing=print $second && git rev-list $second &&
Signature · 1 line
gitgitgadget
PATCH 10/15 scalar: implement the `run` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Note: this subcommand is provided primarily for backwards-compatibility, for existing Scalar uses. It is mostly just a shim for git maintenance, mapping task names from the way Scalar called them to the way Git calls them.
The reason why those names differ? The background maintenance was first implemented in Scalar, and when it was contributed as a patch series implementing the git maintenance command, reviewers suggested better names, those suggestions were accepted before the patches were integrated into core Git.
--- contrib/scalar/scalar.c | 64 +++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 19 ++++++++++++ 2 files changed, 83 insertions(+)
contrib/scalar/scalar.c +64 -0
@@ -490,6 +490,69 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_run (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +struct { +const char *arg, *task; + } tasks[] = { + {"config" ,NULL }, + {"commit-graph" ,"commit-graph" }, + {"fetch" ,"prefetch" }, + {"loose-objects" ,"loose-objects" }, + {"pack-files" ,"incremental-repack" }, + {NULL ,NULL } + }; +struct strbuf buf =STRBUF_INIT ; +const char *usagestr[] = {NULL ,NULL }; +int i; + +strbuf_addstr (&buf,N_ ("scalar run <task> [<enlistment>]\nTasks:\n" )); +for (i =0 ; tasks[i].arg; i++) +strbuf_addf (&buf,"\t%s\n" , tasks[i].arg); + usagestr[0 ] = buf.buf; + + argc =parse_options (argc, argv,NULL , options, + usagestr,0 ); + +if (argc ==0 ) +usage_with_options (usagestr, options); + +if (!strcmp ("all" , argv[0 ])) + i =-1 ; +else { +for (i =0 ; tasks[i].arg &&strcmp (tasks[i].arg, argv[0 ]); i++) + ;/* keep looking for the task */ + +if (i >0 && !tasks[i].arg) { +error (_ ("no such task: '%s'" ), argv[0 ]); +usage_with_options (usagestr, options); + } + } + + argc--; + argv++; +setup_enlistment_directory (argc, argv, usagestr, options,NULL ); +strbuf_release (&buf); + +if (i ==0 ) +return register_dir (); + +if (i >0 ) +return run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL ); + +if (register_dir ()) +return -1 ; +for (i =1 ; tasks[i].arg; i++) +if (run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL )) +return -1 ; +return 0 ; +} +static int remove_deleted_enlistment (struct strbuf *path) {int res =0 ;
@@ -562,6 +625,7 @@ static struct { { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, + { "run", cmd_run }, { NULL, NULL}, };
contrib/scalar/scalar.txt +19 -0
@@ -12,6 +12,7 @@ scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<e scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] DESCRIPTION -----------
@@ -94,6 +95,24 @@ unregister [<enlistment>]:: Remove the specified repository from the list of repositories registered with Scalar and stop the scheduled background maintenance. +Run +~~~ + +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]:: + Run the given maintenance task (or all tasks, if `all` was specified). + Except for `all` and `config`, this subcommand simply hands off to + linkgit:git-maintenance[1] (mapping `fetch` to `prefetch` and + `pack-files` to `incremental-repack`). ++ +These tasks are run automatically as part of the scheduled maintenance, +as soon as the repository is registered with Scalar. It should therefore +not be necessary to run this subcommand manually. ++ +The `config` task is specific to Scalar and configures all those +opinionated default settings that make Git work more efficiently with +large repositories. As this task is run as part of `scalar clone` +automatically, explicit invocations of this task are rarely needed. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH 11/15 scalar: allow reconfiguring an existing enlistment
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
--- contrib/scalar/scalar.c | 81 ++++++++++++++++++++------------ contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 8 ++++ 3 files changed, 68 insertions(+), 29 deletions(-)
contrib/scalar/scalar.c +52 -29
@@ -115,18 +115,20 @@ static int run_git(const char *arg, ...)return res; } -static int set_recommended_config (void ) +static int set_recommended_config (int reconfigure) {struct {const char *key;const char *value; +int overwrite_on_reconfigure; } config[] = { - {"am.keepCR" ,"true" }, - {"core.FSCache" ,"true" }, - {"core.multiPackIndex" ,"true" }, - {"core.preloadIndex" ,"true" }, +/* Required */ + {"am.keepCR" ,"true" ,1 }, + {"core.FSCache" ,"true" ,1 }, + {"core.multiPackIndex" ,"true" ,1 }, + {"core.preloadIndex" ,"true" ,1 }, #ifndef WIN32 - {"core.untrackedCache" ,"true" }, + {"core.untrackedCache" ,"true" ,1 }, #else /* * Unfortunately, Scalar's Functional Tests demonstrated
@@ -140,29 +142,30 @@ static int set_recommended_config(void) * Therefore, with a sad heart, we disable this very useful * feature on Windows. */ - { "core.untrackedCache", "false" }, + { "core.untrackedCache", "false", 1 }, #endif - { "core.bare", "false" }, - { "core.logAllRefUpdates", "true" }, - { "credential.https://dev.azure.com.useHttpPath", "true" }, - { "credential.validate", "false" }, /* GCM4W-only */ - { "gc.auto", "0" }, - { "gui.GCWarning", "false" }, - { "index.threads", "true" }, - { "index.version", "4" }, - { "merge.stat", "false" }, - { "merge.renames", "false" }, - { "pack.useBitmaps", "false" }, - { "pack.useSparse", "true" }, - { "receive.autoGC", "false" }, - { "reset.quiet", "true" }, - { "feature.manyFiles", "false" }, - { "feature.experimental", "false" }, - { "fetch.unpackLimit", "1" }, - { "fetch.writeCommitGraph", "false" }, + { "core.bare", "false", 1 }, + { "core.logAllRefUpdates", "true", 1 }, + { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, + { "credential.validate", "false", 1 }, /* GCM4W-only */ + { "gc.auto", "0", 1 }, + { "gui.GCWarning", "false", 1 }, + { "index.threads", "true", 1 }, + { "index.version", "4", 1 }, + { "merge.stat", "false", 1 }, + { "merge.renames", "false", 1 }, + { "pack.useBitmaps", "false", 1 }, + { "pack.useSparse", "true", 1 }, + { "receive.autoGC", "false", 1 }, + { "reset.quiet", "true", 1 }, + { "feature.manyFiles", "false", 1 }, + { "feature.experimental", "false", 1 }, + { "fetch.unpackLimit", "1", 1 }, + { "fetch.writeCommitGraph", "false", 1 }, #ifdef WIN32 - { "http.sslBackend", "schannel" }, + { "http.sslBackend", "schannel", 1 }, #endif + /* Optional */ { "status.aheadBehind", "false" }, { "commitGraph.generationVersion", "1" }, { "core.autoCRLF", "false" },
@@ -173,7 +176,8 @@ static int set_recommended_config(void)char *value;for (i =0 ; config[i].key; i++) { -if (git_config_get_string (config[i].key, &value)) { +if ((reconfigure && config[i].overwrite_on_reconfigure) || +git_config_get_string (config[i].key, &value)) {trace2_data_string ("scalar" , the_repository, config[i].key,"created" );if (git_config_set_gently (config[i].key, config[i].value) <0 )
@@ -238,7 +242,7 @@ static int register_dir(void)int res =add_or_remove_enlistment (1 );if (!res) - res =set_recommended_config (); + res =set_recommended_config (0 );if (!res) res =toggle_maintenance (1 );
@@ -425,7 +429,7 @@ static int cmd_clone(int argc, const char **argv) (res = run_git("sparse-checkout", "init", "--cone", NULL))) goto cleanup; -if (set_recommended_config ()) +if (set_recommended_config (0 ))return error (_ ("could not configure '%s'" ), dir);if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) {
@@ -490,6 +494,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_reconfigure (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar reconfigure [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); +} +static int cmd_run (int argc,const char **argv) {struct option options[] = {
@@ -626,6 +648,7 @@ static struct { { "register", cmd_register }, { "unregister", cmd_unregister }, { "run", cmd_run }, + { "reconfigure", cmd_reconfigure }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -13,6 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] +scalar reconfigure <enlistment> DESCRIPTION -----------
@@ -113,6 +114,13 @@ opinionated default settings that make Git work more efficiently with large repositories. As this task is run as part of `scalar clone` automatically, explicit invocations of this task are rarely needed. +Reconfigure +~~~~~~~~~~~ + +After a Scalar upgrade, or when the configuration of a Scalar enlistment +was somehow corrupted or changed by mistake, this subcommand allows to +reconfigure the enlistment. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +8 -0
@@ -65,4 +65,12 @@ test_expect_success 'scalar clone' ' ) ' +test_expect_success 'scalar reconfigure' ' + git init one/src && + scalar register one && + git -C one/src config core.preloadIndex false && + scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" +' + test_done
Signature · 1 line
gitgitgadget
PATCH 12/15 scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
After a Scalar upgrade, it can come in really handy if there is an easy way to reconfigure all Scalar enlistments. This new option offers this functionality.
--- contrib/scalar/scalar.c | 61 ++++++++++++++++++++++++++++++-- contrib/scalar/scalar.txt | 10 ++++-- contrib/scalar/t/t9099-scalar.sh | 3 ++ 3 files changed, 68 insertions(+), 6 deletions(-)
contrib/scalar/scalar.c +58 -3
@@ -494,22 +494,77 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int get_scalar_repos (const char *key,const char *value,void *data) +{ +struct string_list *list = data; + +if (!strcmp (key,"scalar.repo" )) +string_list_append (list, value); + +return 0 ; +} +static int cmd_reconfigure (int argc,const char **argv) { +int all =0 ;struct option options[] = { +OPT_BOOL ('a' ,"all" , &all, +N_ ("reconfigure all registered enlistments" )),OPT_END (), };const char *const usage[] = { -N_ ("scalar reconfigure [<enlistment>]" ), +N_ ("scalar reconfigure [--all | <enlistment>]" ),NULL }; +struct string_list scalar_repos =STRING_LIST_INIT_DUP ; +int i, res =0 ; +struct repository r = {NULL }; +struct strbuf commondir =STRBUF_INIT , gitdir =STRBUF_INIT ; argc =parse_options (argc, argv,NULL , options, usage,0 ); -setup_enlistment_directory (argc, argv, usage, options,NULL ); +if (!all) { +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); + } + +if (argc >0 ) +usage_msg_opt (_ ("--all or <enlistment>, but not both" ), + usage, options); + +git_config (get_scalar_repos, &scalar_repos); -return set_recommended_config (1 ); +for (i =0 ; i < scalar_repos.nr; i++) { +const char *dir = scalar_repos.items[i].string; + +strbuf_reset (&commondir); +strbuf_reset (&gitdir); + +if (chdir (dir) <0 ) { +warning_errno (_ ("could not switch to '%s'" ), dir); + res =-1 ; + }else if (discover_git_directory (&commondir, &gitdir) <0 ) { +warning_errno (_ ("git repository gone in '%s'" ), dir); + res =-1 ; + }else { +git_config_clear (); + + the_repository = &r; + r.commondir = commondir.buf; + r.gitdir = gitdir.buf; + +if (set_recommended_config (1 ) <0 ) + res =-1 ; + } + } + +string_list_clear (&scalar_repos,1 ); +strbuf_release (&commondir); +strbuf_release (&gitdir); + +return res; }static int cmd_run (int argc,const char **argv)
contrib/scalar/scalar.txt +7 -3
@@ -13,7 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] -scalar reconfigure <enlistment> +scalar reconfigure [ --all | <enlistment> ] DESCRIPTION -----------
@@ -32,8 +32,8 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `clone` and `list`, all -subcommands expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone`, `list` and +`reconfigure --all`, all subcommands expect to be run in an enlistment. COMMANDS --------
@@ -121,6 +121,10 @@ After a Scalar upgrade, or when the configuration of a Scalar enlistment was somehow corrupted or changed by mistake, this subcommand allows to reconfigure the enlistment. +With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time Scalar +was upgraded. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +3 -0
@@ -70,6 +70,9 @@ test_expect_success 'scalar reconfigure' ' scalar register one && git -C one/src config core.preloadIndex false && scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" && + git -C one/src config core.preloadIndex false && + scalar reconfigure -a && test true = "$(git -C one/src config core.preloadIndex)" '
Signature · 1 line
gitgitgadget
PATCH 13/15 scalar: implement the `delete` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Matthew John Cheetham <[email protected]>
Delete an enlistment by first unregistering the repository and then deleting the enlistment directory (usually the directory containing the worktree src/ directory).
On Windows, if the current directory is inside the enlistment's directory, change to the parent of the enlistment directory, to allow us to delete the enlistment (directories used by processes e.g. as current working directories cannot be deleted on Windows).
--- contrib/scalar/scalar.c | 55 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 +++++ contrib/scalar/t/t9099-scalar.sh | 9 ++++++ 3 files changed, 72 insertions(+)
contrib/scalar/scalar.c +55 -0
@@ -8,6 +8,7 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -334,6 +335,33 @@ static char *remote_default_branch(const char *url)return NULL ; } +static int delete_enlistment (struct strbuf *enlistment) +{ +#ifdef WIN32 +struct strbuf parent =STRBUF_INIT ; +#endif + +if (unregister_dir ()) +die (_ ("failed to unregister repository" )); + +#ifdef WIN32 +/* +* Change the current directory to one outside of the enlistment so +* that we may delete everything underneath it. +*/ +strbuf_addbuf (&parent, enlistment); +strbuf_parent_directory (&parent); +if (chdir (parent.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), parent.buf); +strbuf_release (&parent); +#endif + +if (remove_dir_recursively (enlistment,0 )) +die (_ ("failed to delete enlistment directory" )); + +return 0 ; +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -694,6 +722,32 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar delete <enlistment>" ), +NULL + }; +struct strbuf enlistment =STRBUF_INIT ; +int res =0 ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=1 ) +usage_with_options (usage, options); + +setup_enlistment_directory (argc, argv, usage, options, &enlistment); + + res =delete_enlistment (&enlistment); +strbuf_release (&enlistment); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -704,6 +758,7 @@ static struct { { "unregister", cmd_unregister }, { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, + { "delete", cmd_delete }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -14,6 +14,7 @@ scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] scalar reconfigure [ --all | <enlistment> ] +scalar delete <enlistment> DESCRIPTION -----------
@@ -125,6 +126,13 @@ With the `--all` option, all enlistments currently registered with Scalar will be reconfigured. This option is meant to to be run every time Scalar was upgraded. +Delete +~~~~~~ + +delete <enlistment>:: + This subcommand lets you delete an existing Scalar enlistment from your + local file system, unregistering the repository. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +9 -0
@@ -76,4 +76,13 @@ test_expect_success 'scalar reconfigure' ' test true = "$(git -C one/src config core.preloadIndex)" ' +test_expect_success 'scalar delete without enlistment shows a usage' ' + test_expect_code 129 scalar delete +' + +test_expect_success 'scalar delete with enlistment' ' + scalar delete cloned && + test_path_is_missing cloned +' + test_done
Signature · 1 line
gitgitgadget
PATCH 15/15 scalar: accept -C and -c options before the subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The git executable has these two very useful options:
-C <directory>:
switch to the specified directory before performing any actions
-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommand
With this commit, we teach the scalar executable the same trick.
--- contrib/scalar/scalar.c | 22 +++++++++++++++++++++- contrib/scalar/scalar.txt | 10 ++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +21 -1
@@ -806,6 +806,25 @@ int cmd_main(int argc, const char **argv)struct strbuf scalar_usage =STRBUF_INIT ;int i; +while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ; + argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + } +if (argc >1 ) { argv++; argc--;
@@ -816,7 +835,8 @@ int cmd_main(int argc, const char **argv) }strbuf_addstr (&scalar_usage, -N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +N_ ("scalar [-C <directory>] [-c <key>=<value>] " +"<command> [<options>]\n\nCommands:\n" ));for (i =0 ; builtins[i].name; i++)strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name);
contrib/scalar/scalar.txt +10 -0
@@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options depending on the subcommand. With the exception of `clone`, `list` and `reconfigure --all`, all subcommands expect to be run in an enlistment. +The following options can be specified _before_ the subcommand: + +-C <directory>:: + Before running the subcommand, change the working directory. This + option imitates the same option of linkgit:git[1]. + +-c <key>=<value>:: + For the duration of running the specified subcommand, configure this + setting. This option imitates the same option of linkgit:git[1]. + COMMANDS --------
Signature · 1 line
gitgitgadget
PATCH 14/15 scalar: implement the `version` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The .NET version of Scalar has a version command. This was necessary because it was versioned independently of Git.
Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as git versions. For backwards-compatibility with the .NET version, scalar version prints to stderr, though (git version prints to stdout instead).
--- contrib/scalar/scalar.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
contrib/scalar/scalar.c +39 -0
@@ -9,6 +9,7 @@#include "run-command.h" #include "refs.h" #include "dir.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -362,6 +363,15 @@ static int delete_enlistment(struct strbuf *enlistment)return 0 ; } +/* +* Dummy implementation; Using `get_version_info()` would cause a link error +* without this. +*/ +void load_builtin_commands (const char *prefix,struct cmdnames *cmds) +{ +die ("not implemented" ); +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -748,6 +758,34 @@ static int cmd_delete(int argc, const char **argv)return res; } +static int cmd_version (int argc,const char **argv) +{ +int verbose =0 , build_options =0 ; +struct option options[] = { +OPT__VERBOSE (&verbose,N_ ("include Git version" )), +OPT_BOOL (0 ,"build-options" , &build_options, +N_ ("include Git's build options" )), +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar verbose [-v | --verbose] [--build-options]" ), +NULL + }; +struct strbuf buf =STRBUF_INIT ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=0 ) +usage_with_options (usage, options); + +get_version_info (&buf, build_options); +fprintf (stderr,"%s\n" , buf.buf); +strbuf_release (&buf); + +return 0 ; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -759,6 +797,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
Quoted 3 lines · Johannes Schindelin via GitGitGadget
tl;dr: This series contributes the Scalar command to the Git project. This command provides an opinionated way to create and configure repositories with a focus on very large repositories.
I want to give Johannes a big thanks for organizing this RFC. As you can see from the authorship of the patches, this was an amazingly collaborative effort, but Johannes led the way by creating a base that the rest of us could work with, then finally he brought in all of the gritty details to finish the effort.
Quoted 2 lines
Background
==========
...
Quoted 3 lines
The Scalar project
was created to make that separation, refine the key concepts, and then extract those features into the new Scalar command.
When people have asked me how Scalar fits with the core Git client, I point them to our "Philosophy of Scalar" document [1]. The most concise summary of our goals since starting Scalar has been that Scalar aligns with features already within Git that enable scale. I've said several times that we are constantly making Scalar do less by making Git do more.
[1] https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/philosophy.md
Here is an example: when our large, internal customer told us that they required Linux support for Scalar, we looked at what it would take. We could have done the necessary platform-specific things to convince .NET Core to create a long-running process that launched Git maintenance tasks at different intervals, creating a similar mechanism to the Windows and macOS services that did those operations. But we also knew that the existing system was stuck with architectural decisions from VFS for Git that were not actually in service of how Scalar worked. Instead, we decided to build background maintenance into Git itself and had our Linux port of Scalar run "git maintenance start".
Once the Linux port was proven out with Git's background maintenance, we realized that the window where a user actually interacts with Scalar instead of Git is extremely narrow: users run "scalar clone" or "scalar register" and otherwise only run Git commands. The Scalar process does not need to exist outside of that. (There are some other helpers that can be used in a pinch to diagnose and fix problems, but they are rarely used. These commands, such as 'scalar diagnose' can be contributed separately.)
It became clear that for our own needs it would be easier to ship one installer that included the microsoft/git fork and the Scalar CLI, and it would be simple to rewrite the Scalar CLI with all of the Git helper APIs. We organized the code in a way that we thought would be amenable to an upstream contribution (by placing in contrib/ and using Git code style).
The thing about these commands is that they are _opinionated_. We rely on these opinions for important internal users, but we realize that they are not necessarily optimal for all users. Hence, we did not think it wise to push those opinions onto the 'git' executable. Having 'scalar' continue to live as a separate executable made sense to us.
I believe that by contributing Scalar to the full community, that we create opportunities for Git in the future. For one, users and Git distributors can opt into compiling Scalar so it is more available to users who are interested. Another hopeful idea is that maybe this reinvigorates ideas of how to streamline Git clones for large repos without users needing to learn each and every knob to twist to get things working. Since the Scalar CLI is contributed in the full license of the Git project, pieces of it can be adapted into Git proper as needed.
I look forward to hearing your thoughts.
Thanks,
-Stolee
Re: [PATCH 12/15] scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Johannes Schindelin via GitGitGadgetcontrib/scalar/scalar.txt After a Scalar upgrade, or when the configuration of a Scalar enlistment · line 123
Quoted 11 lines · Johannes Schindelin via GitGitGadget
After a Scalar upgrade, it can come in really handy if there is an easy way to reconfigure all Scalar enlistments. This new option offers this functionality.
Signed-off-by Johannes Schindelin <[email protected]>---
contrib/scalar/scalar.txt +3
-0@@ -121,6 +121,10 @@ After a Scalar upgrade, or when the configuration of a Scalar enlistment +With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time Scalar +was upgraded.
s/was/is/
Re: [PATCH 14/15] scalar: implement the `version` command
In reply to Johannes Schindelin via GitGitGadgetQuoted 6 lines · Johannes Schindelin via GitGitGadget
The .NET version of Scalar has a
versioncommand. This was necessary because it was versioned independently of Git.Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as
git versions. For backwards-compatibility with the .NET version,
s/versions/version/
Quoted 4 lines
scalar versionprints tostderr, though (git versionprints tostdoutinstead).Signed-off-by Johannes Schindelin <[email protected]>
Re: [PATCH 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Derrick Stolee via GitGitGadgetQuoted 9 lines · Derrick Stolee via GitGitGadget
[...]
contrib/scalar/scalar.c +8
-0+#ifndef WIN32 + { "core.untrackedCache", "true" }, +#else + /* + * Unfortunately, Scalar's Functional Tests demonstrated + * that the untracked cache feature is unreliable on Windows + * (which is a bummer because that platform would benefit the + * most from it). For some reason, freshly created files seem
contrib/scalar/scalar.c line 143
Quoted 10 lines
contrib/scalar/scalar.c +9
-0+ * not to update the directory's `lastModified` time + * immediately, but the untracked cache would need to rely on + * that. + * + * Therefore, with a sad heart, we disable this very useful + * feature on Windows. + */ + { "core.untrackedCache", "false" }, +#endif
Ok, but why the need to set it to "false" explicitly? Does it need to be so opinionated as to overwrite existing user-set config in these cases?
contrib/scalar/scalar.c line 144
Quoted 1 line
contrib/scalar/scalar.c +1
-0+ { "core.bare", "false" },
Shouldn't this be set by "git init" already?
contrib/scalar/scalar.c line 145
Quoted 2 lines
[...]
contrib/scalar/scalar.c +1
-0+ { "core.logAllRefUpdates", "true" },
An opinionated thing unrelated to performance?
contrib/scalar/scalar.c line 159
Quoted 3 lines
[...]
contrib/scalar/scalar.c +2
-0+ { "feature.manyFiles", "false" }, + { "feature.experimental", "false" },
Ditto the question about the need to set this, these are false by default, right?
contrib/scalar/scalar.c line 184
Quoted 11 lines
[...]
contrib/scalar/scalar.c +10
-0+if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + }
The commit message doesn't discuss these trace2 additions, these in particular seem like they might be useful, but better done as as some more general trace2 intergration in config.c, i.e. if the functions being called here did the same logging on config set/get.
Re: [PATCH 03/15] scalar: create test infrastructure
In reply to Johannes Schindelin via GitGitGadgetQuoted 5 lines · Johannes Schindelin via GitGitGadget
To test the Scalar command, create a test script in contrib/scalar/t that is executed as
make -C contrib/scalar test. Since Scalar has no meaningful capabilities yet, the only test is rather simple. We will add more tests in subsequent commits that introduce corresponding, new functionality.
As a comment on 01..03/15: I'd really prefer if we stop using this pattern of sub-Makefile, the dependencies are a pain to manage, and we end up copy/pasting large sets of functionality.
That would mean just adding the build of this command to the top-level Makefile behind some "CONTRIB_SCALAR" flag or whatever, but I find that much cleaner than....
contrib/scalar/Makefile include ../../config.mak.uname
Quoted 11 lines
contrib/scalar/Makefile +2
-1@@ -21,7 +22,7 @@ include ../../config.mak.uname TARGETS = scalar$(X) scalar.o GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a -all: scalar$X +all: scalar$X ../../bin-wrappers/scalar +../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
...things like this, which refer to assets built by other Makefiles, and need to plaster over the dependency issues...
Quoted 70 lines
+++ b/contrib/scalar/t/Makefile
contrib/scalar/t/Makefile new file +68
-0@@ -0,0 +1,78 @@ +# Run scalar tests +# +# Copyright (c) 2005,2021 Junio C Hamano, Johannes Schindelin +# + +-include ../../../config.mak.autogen +-include ../../../config.mak + +SHELL_PATH ?= $(SHELL) +PERL_PATH ?= /usr/bin/perl +RM ?= rm -f +PROVE ?= prove +DEFAULT_TEST_TARGET ?= test +TEST_LINT ?= test-lint + +ifdef TEST_OUTPUT_DIRECTORY +TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results +else +TEST_RESULTS_DIRECTORY = ../../../t/test-results +endif + +# Shell quote; +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) + +T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) + +all: $(DEFAULT_TEST_TARGET) + +test: $(TEST_LINT) + $(MAKE) aggregate-results-and-cleanup + +prove: $(TEST_LINT) + @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) + $(MAKE) clean-except-prove-cache + +$(T): + @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +clean-except-prove-cache: + $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' + $(RM) -r valgrind/bin + +clean: clean-except-prove-cache + $(RM) .prove + +test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax + +test-lint-duplicates: + @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ + test -z "$$dups" || { \ + echo >&2 "duplicate test numbers:" $$dups; exit 1; } + +test-lint-executable: + @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \ + test -z "$$bad" || { \ + echo >&2 "non-executable tests:" $$bad; exit 1; } + +test-lint-shell-syntax: + @'$(PERL_PATH_SQ)' ../../../t/check-non-portable-shell.pl $(T) + +aggregate-results-and-cleanup: $(T) + $(MAKE) aggregate-results + $(MAKE) clean + +aggregate-results: + for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \
contrib/scalar/t/Makefile line 78
Quoted 10 lines
contrib/scalar/t/Makefile new file +10
-0+ echo "$$f"; \ + done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh + +valgrind: + $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" + +test-results: + mkdir -p test-results + +.PHONY: $(T) aggregate-results clean valgrind
...and this entire copy/pasting & adjusting of t/Makefile.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetQuoted 6 lines · Johannes Schindelin via GitGitGadget
This implements Scalar's opinionated
clonecommand: it tries to use a partial clone and sets up a sparse checkout by default. In contrast togit clone,scalar clonesets up the worktree in thesrc/subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Perhaps nobody else wondered this while reading this, but I thought this might be some sparse/worktree magic where cloning into "foo" would have "foo/.git", but the worktree was somehow magically mapped at foo/src/".
But no, it just takes your "scalar clone <url> foo" and translates it to "foo/src", so you'll get a directory at "foo".
Quoted 3 lines
Note: We intentionally use a slightly wasteful
set_config()function (which does not reuse a singlestrbuf, for example, though performance _really_ does not matter here) for convenience and readability.
FWIW I think the commit message could do without this, that part of the code is obviously not performance sensitive at all. But maybe an explicit note helps anyway...
Re: [PATCH 10/15] scalar: implement the `run` command
In reply to Derrick Stolee via GitGitGadgetcontrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 509
Quoted 1 line · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +1
-0+const char *usagestr[] = {NULL ,NULL };
Missing usage strings?
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 520
Quoted 1 line
contrib/scalar/scalar.c +1
-0+if (argc ==0 )
Style nit (per style guide): s/argc == 0/!argc/g.
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 524
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+if (!strcmp ("all" , argv[0 ])) + i =-1 ;
Style nit (per style guide): missing braces here.
(Just noting the style nits once, but more in this patch, and presumably the rest of the series...)
Re: [PATCH 11/15] scalar: allow reconfiguring an existing enlistment
In reply to Johannes Schindelin via GitGitGadgetcontrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 123
Quoted 7 lines · Johannes Schindelin via GitGitGadget
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
[...]
contrib/scalar/scalar.c +1
-0const char *key;const char *value; +int overwrite_on_reconfigure;
If you make this a "keep_on_reconfigure", then ...
contrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 129
Quoted 10 lines
contrib/scalar/scalar.c +5
-4} config[] = { - { "am.keepCR", "true" }, - { "core.FSCache", "true" }, - { "core.multiPackIndex", "true" }, - { "core.preloadIndex", "true" }, +/* Required */ + { "am.keepCR", "true", 1 }, + { "core.FSCache", "true", 1 }, + { "core.multiPackIndex", "true", 1 }, + { "core.preloadIndex", "true", 1 },
You won't need the churn/boilerplate of adding "1" to everything here, but can just change the initial patch to use designated initializers.
That along with a throwaway macro like:
#define SCALAR_CFG_TRUE(k) (.key = k, .value = "true") #define SCALAR_CFG_FALSE(k) (.key = k, .value = "false")
Might (or might not) make this even easier to eyeball...
Re: [PATCH 15/15] scalar: accept -C and -c options before the subcommand
In reply to Johannes Schindelin via GitGitGadgetQuoted 19 lines · Johannes Schindelin via GitGitGadget
The
gitexecutable has these two very useful options:-C <directory>:
switch to the specified directory before performing any actions-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommandWith this commit, we teach the
scalarexecutable the same trick. [...]contrib/scalar/scalar.c +8
-0+while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ;
contrib/scalar/scalar.c int cmd_main(int argc, const char **argv) · line 826
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + }
This along with my earlier comment about the Makefile copy/pasting makes me wonder if an easier way to integrate this wouldn't be to refactor git.c a bit to have it understand either "git" or "scalar", then instead of "ls-tree" etc. as "git" the subcommands would become "built-ins".
Which would give us both "[git|scalar] [-c ...] <cmd>" for free, and elimante the need for the inevetable future divergence of wanting -p, -P, --exec-path etc. in both.
Re: [PATCH 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Ævar Arnfjörð BjarmasonQuoted 17 lines · Ævar Arnfjörð Bjarmason
Quoted 14 lines · Derrick Stolee via GitGitGadget
[...]
contrib/scalar/scalar.c +13
-0+#ifndef WIN32 + { "core.untrackedCache", "true" }, +#else + /* + * Unfortunately, Scalar's Functional Tests demonstrated + * that the untracked cache feature is unreliable on Windows + * (which is a bummer because that platform would benefit the + * most from it). For some reason, freshly created files seem + * not to update the directory's `lastModified` time + * immediately, but the untracked cache would need to rely on + * that. + * + * Therefore, with a sad heart, we disable this very useful
Quoted 8 lines
contrib/scalar/scalar.c line 143
Quoted 5 lines
contrib/scalar/scalar.c +4
-0+ * feature on Windows. + */ + { "core.untrackedCache", "false" }, +#endifOk, but why the need to set it to "false" explicitly? Does it need to be so opinionated as to overwrite existing user-set config in these cases?
Users can overwrite this local config value, but this is placed to avoid a global config value from applying specifically within Scalar-created repos.
Quoted 3 lines
contrib/scalar/scalar.c line 144
Quoted 1 line
contrib/scalar/scalar.c +1
-0+ { "core.bare", "false" },Shouldn't this be set by "git init" already?
This one is probably a bit _too_ defensive. It can be removed.
Quoted 4 lines
contrib/scalar/scalar.c line 145
Quoted 2 lines
[...]
contrib/scalar/scalar.c +1
-0+ { "core.logAllRefUpdates", "true" },An opinionated thing unrelated to performance?
It's an opinionated thing related to supporting monorepo users. It helps us diagnose issues they have by recreating a sequence of events.
Quoted 6 lines
contrib/scalar/scalar.c line 159
Quoted 3 lines
[...]
contrib/scalar/scalar.c +2
-0+ { "feature.manyFiles", "false" }, + { "feature.experimental", "false" },Ditto the question about the need to set this, these are false by default, right?
But if a user has them on globally, then we don't want them to apply locally (in favor of the settings that we set explicitly).
Quoted 8 lines
Quoted 8 lines
[...]
contrib/scalar/scalar.c +7
-0+if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else {
Quoted 8 lines
contrib/scalar/scalar.c line 184
Quoted 3 lines
contrib/scalar/scalar.c +3
-0+trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + }The commit message doesn't discuss these trace2 additions, these in particular seem like they might be useful, but better done as as some more general trace2 intergration in
config.c, i.e. if the functions being called here did the same logging on config set/get.
If we want to do such a tracing change within git_config_set*(), then that would be an appropriate replacement. The biggest reason to include them here is to trace that an existing value already exists, for the case of running 'scalar reconfigure' during an upgrade. That part doesn't make much sense to put into config.c.
Thanks,
-Stolee
Re: [PATCH 15/15] scalar: accept -C and -c options before the subcommand
In reply to Ævar Arnfjörð BjarmasonQuoted 38 lines · Ævar Arnfjörð Bjarmason
Quoted 19 lines · Johannes Schindelin via GitGitGadget
The
gitexecutable has these two very useful options:-C <directory>:
switch to the specified directory before performing any actions-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommandWith this commit, we teach the
scalarexecutable the same trick. [...]contrib/scalar/scalar.c +8
-0+while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ;contrib/scalar/scalar.c int cmd_main(int argc, const char **argv) · line 826
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + }This along with my earlier comment about the
Makefilecopy/pasting makes me wonder if an easier way to integrate this wouldn't be to refactorgit.ca bit to have it understand either "git" or "scalar", then instead of "ls-tree" etc. as "git" the subcommands would become "built-ins".
Quoted 3 lines
Which would give us both "[git|scalar] [-c ...] <cmd>" for free, and elimante the need for the inevetable future divergence of wanting -p, -P, --exec-path etc. in both.
Such a change would likely eliminate the ability to not include Scalar when building the Git codebase, which we tried to avoid by keeping it within contrib and have it be compiled via an opt-in flag.
If we want to talk about integrating Scalar into Git in a deeper way, then that is an interesting discussion to have, but it lives at a much higher level than Makefile details.
The questions we are really looking to answer in this RFC are:
1. Will the Git project accept Scalar into its codebase?
2. What is the best place for Scalar to live in the Git codebase?
We erred on the side of keeping Scalar as optional as possible. If the community is more interested in a deeper integration, then that could be an interesting direction.
In my opinion, I think the current tactic is safest. We could always decide on a deeper integration later by moving the code around. It seems harder to do the reverse.
Thanks,
-Stolee
Re: [PATCH 15/15] scalar: accept -C and -c options before the subcommand
Quoted 39 lines · Derrick Stolee
Quoted 38 lines · Ævar Arnfjörð Bjarmason
Quoted 19 lines · Johannes Schindelin via GitGitGadget
The
gitexecutable has these two very useful options:-C <directory>:
switch to the specified directory before performing any actions-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommandWith this commit, we teach the
scalarexecutable the same trick. [...]contrib/scalar/scalar.c +8
-0+while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ;contrib/scalar/scalar.c int cmd_main(int argc, const char **argv) · line 826
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + }This along with my earlier comment about the
Makefilecopy/pasting makes me wonder if an easier way to integrate this wouldn't be to refactorgit.ca bit to have it understand either "git" or "scalar", then instead of "ls-tree" etc. as "git" the subcommands would become "built-ins".
Quoted 7 lines
Quoted 3 lines
Which would give us both "[git|scalar] [-c ...] <cmd>" for free, and elimante the need for the inevetable future divergence of wanting -p, -P, --exec-path etc. in both.
Such a change would likely eliminate the ability to not include Scalar when building the Git codebase, which we tried to avoid by keeping it within contrib and have it be compiled via an opt-in flag.
I mean to still have it behind a flag, but to handle it similar to how we handle NO_CURL, EXCLUDED_PROGRAMS and the like, i.e. not requiring parallel maintenance of copy/pasted Makefile logic in contrib/.
Quoted 3 lines
If we want to talk about integrating Scalar into Git in a deeper way, then that is an interesting discussion to have, but it lives at a much higher level than
Makefiledetails.
To be clear I'm proposing no change at all in term of what happens when you run "make install", just commenting on the implementation details of how we arrange for things to be built and configured before that step.
I realize that this is following some prior art of
e.g. contrib/subtree/Makefile, but IMNSHO that approach is a historical mistake we should be backing out of. There was some recent discussion of this here:
https://lore.kernel.org/git/[email protected]/
E.g. now we have some painful management of the depencency graph between /Makefile and Documentation/Makefile requiring fixes like 56550ea7180 (Makefile: add missing dependencies of 'config-list.h', 2021-04-08), adding yet another Makefile into the mix which (to take one example) depends on doc.dep, which in turn depends on ...; It's all a bunch of needless complexity we can avoid.
Quoted 9 lines
The questions we are really looking to answer in this RFC are:
1. Will the Git project accept Scalar into its codebase?
2. What is the best place for Scalar to live in the Git codebase?
We erred on the side of keeping Scalar as optional as possible. If the community is more interested in a deeper integration, then that could be an interesting direction.
Indeed, to be clear I realize I'm entirely punting on the real questions you're interested in. I just gave this an initial cursory skimming for now, I have not formed an informed opinion on your #1, but just a little bit of #2.
My initial reaction to #1 without having looked into it deeply is some combination of "sure, why not?", and that the people/group contributing major scalability work to git.git should be given the benefit of the doubt. Maybe we won't keep "scalar" long-term, or change its UI etc., all of that can be handled in some carefully worded documentation somewhere.
Of course all these suggestions I'm making about Makefile arrangement are rather pointless if there isn't consensus to get past the hurdle of your #1.
Quoted 3 lines
In my opinion, I think the current tactic is safest. We could always decide on a deeper integration later by moving the code around. It seems harder to do the reverse.
I think "deeper integration" is the reverse of what you think it is.
I.e. if I'm patching or maintaining part of the Makefile logic to it's deeper (or perhaps "gnarlier" is the righ word?) integration to need to duplicate that work in two places, or always take into account that some not-built-by-default-but-quite-common command's *.txt docs and *.sh tests live in some unusual place for the purposes of CI, lint, tooling etc.
In other words, it's a question of how much net complexity is being added to the (build) system. That complexity doesn't automatically reduce just because some files live in another directory, sometimes that's an increase in complexity.
Whereas just conditionally adding it to some list in the top-level Makefile (or Documentation/Makefile) is relatively maintenance-free, and to our users / packagers the result should be the same or near enough. It won't matter to them if building the optional thing is another "make" command or just a flag to the existing "make" command.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Ævar Arnfjörð BjarmasonQuoted 8 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Johannes Schindelin via GitGitGadget
Note: We intentionally use a slightly wasteful
set_config()function (which does not reuse a singlestrbuf, for example, though performance _really_ does not matter here) for convenience and readability.FWIW I think the commit message could do without this, that part of the code is obviously not performance sensitive at all. But maybe an explicit note helps anyway...
FWIW, I also found this distracting; it takes the reader's attention away from more important aspects of the patch. (But it alone is not worth a re-roll; it was just a minor hiccup.)
Quoted 71 lines · Derrick Stolee
Quoted 3 lines · Johannes Schindelin via GitGitGadget
tl;dr: This series contributes the Scalar command to the Git project. This command provides an opinionated way to create and configure repositories with a focus on very large repositories.
I want to give Johannes a big thanks for organizing this RFC. As you can see from the authorship of the patches, this was an amazingly collaborative effort, but Johannes led the way by creating a base that the rest of us could work with, then finally he brought in all of the gritty details to finish the effort.
Quoted 2 lines
Background
==========...
Quoted 3 lines
The Scalar project
was created to make that separation, refine the key concepts, and then extract those features into the new Scalar command.When people have asked me how Scalar fits with the core Git client, I point them to our "Philosophy of Scalar" document [1]. The most concise summary of our goals since starting Scalar has been that Scalar aligns with features already within Git that enable scale. I've said several times that we are constantly making Scalar do less by making Git do more.
[1] https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/philosophy.md
Here is an example: when our large, internal customer told us that they required Linux support for Scalar, we looked at what it would take. We could have done the necessary platform-specific things to convince .NET Core to create a long-running process that launched Git maintenance tasks at different intervals, creating a similar mechanism to the Windows and macOS services that did those operations. But we also knew that the existing system was stuck with architectural decisions from VFS for Git that were not actually in service of how Scalar worked. Instead, we decided to build background maintenance into Git itself and had our Linux port of Scalar run "git maintenance start".
Once the Linux port was proven out with Git's background maintenance, we realized that the window where a user actually interacts with Scalar instead of Git is extremely narrow: users run "scalar clone" or "scalar register" and otherwise only run Git commands. The Scalar process does not need to exist outside of that. (There are some other helpers that can be used in a pinch to diagnose and fix problems, but they are rarely used. These commands, such as 'scalar diagnose' can be contributed separately.)
It became clear that for our own needs it would be easier to ship one installer that included the microsoft/git fork and the Scalar CLI, and it would be simple to rewrite the Scalar CLI with all of the Git helper APIs. We organized the code in a way that we thought would be amenable to an upstream contribution (by placing in contrib/ and using Git code style).
The thing about these commands is that they are _opinionated_. We rely on these opinions for important internal users, but we realize that they are not necessarily optimal for all users. Hence, we did not think it wise to push those opinions onto the 'git' executable. Having 'scalar' continue to live as a separate executable made sense to us.
I believe that by contributing Scalar to the full community, that we create opportunities for Git in the future. For one, users and Git distributors can opt into compiling Scalar so it is more available to users who are interested. Another hopeful idea is that maybe this reinvigorates ideas of how to streamline Git clones for large repos without users needing to learn each and every knob to twist to get things working. Since the Scalar CLI is contributed in the full license of the Git project, pieces of it can be adapted into Git proper as needed.
Quoted 4 lines
I look forward to hearing your thoughts.
Thanks,
-Stolee
Looks like exciting stuff, you two. I'm behind on review as it is; I still need to get back to Stolee's sparse-index add/rm/mv series, but I'll try to circle back and take a look.
Re: [PATCH 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Derrick Stolee via GitGitGadgetQuoted 26 lines · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +26
-0+static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); + do { + const size_t len = path.len; + + /* check if currently in enlistment root with src/ workdir */
contrib/scalar/scalar.c line 61
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + }
This special casing of "normally the top of the working tree is enlisted, but if the repository is called src/, then we enslist one level up" is a bit of eyesore because
(1) it is unclear why such a directory with 'src/' subdirectory is so special, and
(2) it fails to serve those who has the same need but named their source subdirectory differently (like 'source/').
"The design decisions we made are all part of being opinionated" can all explain it away, but at least we should let the users know where the opinionated choices scalar makes want to lead them to, and this "src/" stuff needs a bit of clarification. Perhaps a documentation will be added in later steps?
contrib/scalar/scalar.c line 184
Quoted 11 lines
contrib/scalar/scalar.c +11
-0+for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + }
I wonder if we should have a table of configuration variables and their default values. The above code implements a skewed "we only avoid overriding what is explicitly configured". A variable that the user left unconfigured because the user found its default satisfactory will be overridden, and if the value scalar wants to use happens to be the default value, we leave an explicit configuration to that default value in the resulting configuration file.
But I think the above is the best we can do without such a central registry of configuration variables.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
contrib/scalar/scalar.c static int unregister_dir(void) · line 290
Quoted 8 lines · writes:
contrib/scalar/scalar.c +8
-0+static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +strbuf_addstr (&out,"-\n" );
Is this a workaround for the problem that the first "ref:" line won't be found by looking for "\nref: " in the loop? Cute, but the extra "-" is a bit misleading.
contrib/scalar/scalar.c static int unregister_dir(void) · line 302
Quoted 12 lines
contrib/scalar/scalar.c +12
-0+if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +char *ref = out.buf; + +while ((ref =strstr (ref +1 ,"\nref: " ))) { +const char *p; +char *head, *branch; + + ref +=strlen ("\nref: " ); + head =strstr (ref,"\tHEAD" ); + +if (!head ||memchr (ref,'\n' , head - ref)) +continue ;
OK. We expect "ref: " <refname> "\t" <head> "\n" where <head> is "HEAD" for their .git/HEAD and refs/remotes/<nick>/HEAD for their remote-tracking branch for the remote they call <nick>, on a single line. We reject a line that is not of that shape, and we reject a line that is about remote-tracking branch by only looking for "\tHEAD". Makes sense.
The strstr() goes from "ref + 1", which feels sloppy. When we reject the line we found that begins with "ref :", I would have expected that the next scan would start at the beginning of the next line, not from the middle of this line at the first letter 'e' in 'refs/heads/' on the current line "ref: refs/heads/.....". As long as the current line is long enough, strstr() would not miss the beginning of the next line, so it might be OK.
contrib/scalar/scalar.c static int unregister_dir(void) · line 313
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+if (skip_prefix (ref,"refs/heads/" , &p)) { + branch =xstrndup (p, head - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(head - ref), ref); +strbuf_release (&out); +return NULL ;
OK. Any symref whose basename is HEAD in their remote-tracking hierarchy would have been rejected earlier in the loop.
Is there a particular reason why we return early here, instead of breaking out of hte loop and let the generic "failed to get" code path below to handle this case?
Quoted 66 lines
contrib/scalar/scalar.c +65
-0+ } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment);
contrib/scalar/scalar.c static int unregister_dir(void) · line 389
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b);
Doesn't "git clone" already use their HEAD without having to make an extra "git ls-remote" roundtrip?
Ahh, you do not do "git clone"; you do "git init", set things up, and then "git fetch" and checkout, all manually.
Which is kind of shame.
I wonder if it is a cleaner implementation to give a new option to "git clone" that gives a command sequence (not necessarily have to be implemented as a shell script) that specifies necessary pre-configuration steps to be done before the command starts the transfer step.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
Quoted 3 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]> writes:
...
Quoted 16 lines
contrib/scalar/scalar.c static int unregister_dir(void) · line 389
Quoted 9 lines
contrib/scalar/scalar.c +9
-0+ dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b);Doesn't "git clone" already use their HEAD without having to make an extra "git ls-remote" roundtrip?
Ahh, you do not do "git clone"; you do "git init", set things up, and then "git fetch" and checkout, all manually.
Quoted 7 lines
Which is kind of shame.
I wonder if it is a cleaner implementation to give a new option to "git clone" that gives a command sequence (not necessarily have to be implemented as a shell script) that specifies necessary pre-configuration steps to be done before the command starts the transfer step.
I agree that 'git clone' plus maybe some more improvements like '--sparse=cone' to set up cone-mode sparse-checkout would be good. And also the implementation being contributed here is cleaner if we can use 'git clone'.
We are trying to balance a clean upstream implementation with some custom things that we still need in our microsoft/git fork to handle the integration with the GVFS Protocol (i.e. partial clone on Azure Repos). That customization is cleaner to keep here in the scalar code instead of adding an option to 'git clone'. It is difficult to justify code patterns here due to choices we have made in our fork, so I _could_ see a way to replace those custom bits with new, custom flags to 'git clone'. It just requires additional investment during our integration when we incorporate these upstream changes. Naturally, I'm motivated to avoid that extra work.
If your opinion to switch to 'git clone' is a strong one, then I could see us doing that change. I just want you to be aware of the hidden reasons for choices like these.
Thanks,
-Stolee
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Junio C HamanoHi Junio,
Quoted 15 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]>
contrib/scalar/scalar.c static int unregister_dir(void) · line 290
Quoted 8 lines · writes:
contrib/scalar/scalar.c +8
-0+static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +strbuf_addstr (&out,"-\n" );Is this a workaround for the problem that the first "ref:" line won't be found by looking for "\nref: " in the loop? Cute, but the extra "-" is a bit misleading.
The - is actually needed because of the ref + 1 below, over which you stumbled.
Quoted 21 lines
contrib/scalar/scalar.c static int unregister_dir(void) · line 302
Quoted 13 lines
contrib/scalar/scalar.c +12
-0+if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +char *ref = out.buf; + +while ((ref =strstr (ref +1 ,"\nref: " ))) { +const char *p; +char *head, *branch; + + ref +=strlen ("\nref: " ); + head =strstr (ref,"\tHEAD" ); + +if (!head ||memchr (ref,'\n' , head - ref)) +continue ;OK. We expect "ref: " <refname> "\t" <head> "\n" where <head> is "HEAD" for their .git/HEAD and refs/remotes/<nick>/HEAD for their remote-tracking branch for the remote they call <nick>, on a single line. We reject a line that is not of that shape, and we reject a line that is about remote-tracking branch by only looking for "\tHEAD". Makes sense.
Quoted 1 line
The
strstr()goes from "ref + 1", which feels sloppy.
I would use a different adjective, one that is less judgemental in nature, but then, you were talking about your feelings.
Quoted 6 lines
When we reject the line we found that begins with "ref :", I would have expected that the next scan would start at the beginning of the next line, not from the middle of this line at the first letter 'e' in 'refs/heads/' on the current line "ref: refs/heads/.....". As long as the current line is long enough,
strstr()would not miss the beginning of the next line, so it might be OK.
It would even work if the current line is shorter, but as you point out:
it is wasteful. And it could be improved to be more readable. I reworked it, and it now looks like this:
if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
const char *line = out.buf;
while (*line) {
const char *eol = strchrnul(line, '\n'), *p;
size_t len = eol - line;
char *branch;
if (!skip_prefix(line, "ref: ", &p) ||
!strip_suffix_mem(line, &len, "\tHEAD")) {
line = eol + (*eol == '\n');
continue;
}
eol = line + len;
if (skip_prefix(p, "refs/heads/", &p)) {
branch = xstrndup(p, eol - p);
strbuf_release(&out);
return branch;
}
error(_("remote HEAD is not a branch: '%.*s'"),
(int)(eol - p), p);
strbuf_release(&out);
return NULL;
}
}It now parses the output line by line, looking for the expected prefix and suffix, then verifies the ref name format, and either returns the short branch name or errors out with the message that this is not a branch.
Quoted 15 lines
contrib/scalar/scalar.c static int unregister_dir(void) · line 313
Quoted 11 lines
contrib/scalar/scalar.c +10
-0+if (skip_prefix (ref,"refs/heads/" , &p)) { + branch =xstrndup (p, head - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(head - ref), ref); +strbuf_release (&out); +return NULL ;OK. Any symref whose basename is HEAD in their remote-tracking hierarchy would have been rejected earlier in the loop.
Quoted 3 lines
Is there a particular reason why we return early here, instead of breaking out of hte loop and let the generic "failed to get" code path below to handle this case?
Yes, the reason is that I wanted to err on the side of caution. If the remote repository reports a default branch that is not a default branch at all, I do not want to pretend that things are fine and then run into trouble later when we set up a non-branch as remote-tracking target or something like that.
Quoted 84 lines
Quoted 67 lines
contrib/scalar/scalar.c +65
-0+ } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment);contrib/scalar/scalar.c static int unregister_dir(void) · line 389
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b);Doesn't "git clone" already use their HEAD without having to make an extra "git ls-remote" roundtrip?
Ahh, you do not do "git clone"; you do "git init", set things up, and then "git fetch" and checkout, all manually.
Quoted 7 lines
Which is kind of shame.
I wonder if it is a cleaner implementation to give a new option to "git clone" that gives a command sequence (not necessarily have to be implemented as a shell script) that specifies necessary pre-configuration steps to be done before the command starts the transfer step.
Right. It is a shame, I agree. And it is one of the things I want to work on, after the Scalar patch series made it into Git.
The reason why I don't want to work on this now is that I expect this effort to result in new options for git clone, new options that need to be designed well, and where I fully expect a long discussion until we reach a consensus how these options should look like, especially since we will need to maintain backwards-compatibility of Scalar's functionality.
Therefore, in the interest to keep the patch series relatively easy to review, I left this in the "for later" pile, for now.
Ciao,
Dscho
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Eric SunshineHi Eric,
Quoted 7 lines · Eric Sunshine
Quoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Johannes Schindelin via GitGitGadget
Note: We intentionally use a slightly wasteful
set_config()function (which does not reuse a singlestrbuf, for example, though performance _really_ does not matter here) for convenience and readability.
Quoted 7 lines
Quoted 3 lines
FWIW I think the commit message could do without this, that part of the code is obviously not performance sensitive at all. But maybe an explicit note helps anyway...
FWIW, I also found this distracting; it takes the reader's attention away from more important aspects of the patch. (But it alone is not worth a re-roll; it was just a minor hiccup.)
Since I reworked the remote default branch parsing anyway, I removed this paragraph from the commit message.
Ciao,
Dscho
Re: [PATCH 12/15] scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Eric SunshineHi Eric,
Quoted 6 lines · Eric Sunshine
Quoted 4 lines · Johannes Schindelin via GitGitGadget
After a Scalar upgrade, it can come in really handy if there is an easy way to reconfigure all Scalar enlistments. This new option offers this functionality.
Quoted 9 lines
contrib/scalar/scalar.txt After a Scalar upgrade, or when the configuration of a Scalar enlistment · line 123
Quoted 7 lines
Signed-off-by Johannes Schindelin <[email protected]>---
contrib/scalar/scalar.txt +3
-0@@ -121,6 +121,10 @@ After a Scalar upgrade, or when the configuration of a Scalar enlistment +With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time Scalar +was upgraded.s/was/is/
I wanted to convey a temporal order, so I changed it to "every time after Scalar is upgraded". Okay?
Ciao,
Dscho
Re: [PATCH 14/15] scalar: implement the `version` command
In reply to Eric SunshineHi Eric,
Quoted 10 lines · Eric Sunshine
Quoted 6 lines · Johannes Schindelin via GitGitGadget
The .NET version of Scalar has a
versioncommand. This was necessary because it was versioned independently of Git.Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as
git versions. For backwards-compatibility with the .NET version,s/versions/version/
Thank you!
Dscho
Quoted 6 lines
Quoted 5 lines
scalar versionprints tostderr, though (git versionprints tostdoutinstead).Signed-off-by Johannes Schindelin <[email protected]>
Re: [PATCH 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Junio C HamanoHi Junio,
Quoted 43 lines · Junio C Hamano
Quoted 26 lines · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +26
-0+static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); + do { + const size_t len = path.len; + + /* check if currently in enlistment root with src/ workdir */contrib/scalar/scalar.c line 61
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + }This special casing of "normally the top of the working tree is enlisted, but if the repository is called src/, then we enslist one level up" is a bit of eyesore because
Quoted 5 lines
(1) it is unclear why such a directory with 'src/' subdirectory is so special, and
(2) it fails to serve those who has the same need but named their source subdirectory differently (like 'source/').
All true. I wish we had come up with a better way, or with a way to override this via an option.
Unfortunately, we are now bound by the fact that there are already users out there...
Quoted 5 lines
"The design decisions we made are all part of being opinionated" can all explain it away, but at least we should let the users know where the opinionated choices scalar makes want to lead them to, and this "src/" stuff needs a bit of clarification. Perhaps a documentation will be added in later steps?
I had hoped that the initial blurb of the manual page was sufficient, but you're right, the register subcommand is particular in that it allows to force Scalar to consider the worktree to be identical to the Scalar enlistment. I added this:
diff --git a/contrib/scalar/scalar.txt b/contrib/scalar/scalar.txt index 1593da45eae..568987064b2 100644
--- a/contrib/scalar/scalar.txt
+++ b/contrib/scalar/scalar.txt
@@ -40,6 +40,10 @@ register [<enlistment>]::
and starts background maintenance. If <enlistment> is not provided, then the enlistment associated with the current working directory is registered.
++
+Note: when this subcommand is called in a worktree that is called src/, its +parent directory is considered to be the Scalar enlistment. If the worktree is +_not_ called src/, it itself will be considered to be the Scalar enlistment.
Quoted 21 lines
contrib/scalar/scalar.c line 184
Quoted 11 lines
contrib/scalar/scalar.c +11
-0+for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + }I wonder if we should have a table of configuration variables and their default values. The above code implements a skewed "we only avoid overriding what is explicitly configured". A variable that the user left unconfigured because the user found its default satisfactory will be overridden, and if the value scalar wants to use happens to be the default value, we leave an explicit configuration to that default value in the resulting configuration file.
Quoted 2 lines
But I think the above is the best we can do without such a central registry of configuration variables.
Even with such a central registry, there would still be the question whether the user, by staying with the default, wanted Git (or in this instance, Scalar) to keep using the old default. The intention is unfortunately not clear just from setting the variable.
So I think this is the best we can do.
Ciao,
Dscho
Re: [PATCH 10/15] scalar: implement the `run` command
In reply to Ævar Arnfjörð BjarmasonHi Ævar,
Quoted 5 lines · Ævar Arnfjörð Bjarmason
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 509
Quoted 1 line · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +1
-0+const char *usagestr[] = {NULL ,NULL };Missing usage strings?
This command will show a generated usage, i.e. a non-static string. It therefore cannot be specified here already. See the strbuf_*() calls populating buf and the usagestr[0] = buf.buf; assignment.
Quoted 3 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 520
Quoted 1 line
contrib/scalar/scalar.c +1
-0+if (argc ==0 )Style nit (per style guide): s/argc == 0/!argc/g.
It is true that we often do this, but in this instance it would be
misleading: argc is a counter, not a Boolean.
Quoted 4 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 524
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+if (!strcmp ("all" , argv[0 ])) + i =-1 ;Style nit (per style guide): missing braces here.
The style guide specifically allows my preference to leave single-line blocks without curlies.
Ciao,
Johannes
Re: [PATCH 11/15] scalar: allow reconfiguring an existing enlistment
In reply to Ævar Arnfjörð BjarmasonHi Ævar,
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Johannes Schindelin via GitGitGadget
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
Quoted 6 lines
contrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 123
Quoted 4 lines
[...]
contrib/scalar/scalar.c +1
-0const char *key;const char *value; +int overwrite_on_reconfigure;If you make this a "
keep_on_reconfigure", then ...
I do not think that this would be a better name, or that renaming this field would do anything except cause more work for me.
Quoted 15 lines
contrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 129
Quoted 11 lines
contrib/scalar/scalar.c +5
-4} config[] = { - { "am.keepCR", "true" }, - { "core.FSCache", "true" }, - { "core.multiPackIndex", "true" }, - { "core.preloadIndex", "true" }, +/* Required */ + { "am.keepCR", "true", 1 }, + { "core.FSCache", "true", 1 }, + { "core.multiPackIndex", "true", 1 }, + { "core.preloadIndex", "true", 1 },You won't need the churn/boilerplate of adding "1" to everything here, but can just change the initial patch to use designated initializers.
Quoted 6 lines
That along with a throwaway macro like:
#define
SCALAR_CFG_TRUE(k)(.key = k, .value = "true") #defineSCALAR_CFG_FALSE(k)(.key = k, .value = "false")Might (or might not) make this even easier to eyeball...
To me, it makes things less readable. There is an entire section with the header /* Optional */ below, and I want this list to stay as readable as it is now.
Ciao,
Dscho
Re: [PATCH 12/15] scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Johannes SchindelinQuoted 10 lines · Johannes Schindelin
Quoted 6 lines · Eric Sunshine
On Mon, Aug 30, 2021 at 5:35 PM Johannes Schindelin via GitGitGadget
contrib/scalar/scalar.txt After a Scalar upgrade, or when the configuration of a Scalar enlistment · line 126
Quoted 3 lines
contrib/scalar/scalar.txt +3
-0+With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time Scalar +was upgraded.s/was/is/
I wanted to convey a temporal order, so I changed it to "every time after Scalar is upgraded". Okay?
I think I understood the intent of the original, but it causes a grammatical hiccup. Your revised version can work, although I might write it this way:
This option is meant to be run each time Scalar is upgraded.
However, perhaps that is too ambiguous and some users may think that the process of upgrading Scalar will automatically run this command, and you'd like to make it clear that it is the user's responsibility. So, perhaps:
Use this option after each Scalar upgrade.
or something.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Derrick StoleeQuoted 16 lines · Derrick Stolee
Quoted 10 lines
Ahh, you do not do "git clone"; you do "git init", set things up, and then "git fetch" and checkout, all manually.
Which is kind of shame.
I wonder if it is a cleaner implementation to give a new option to "git clone" that gives a command sequence (not necessarily have to be implemented as a shell script) that specifies necessary pre-configuration steps to be done before the command starts the transfer step.
I agree that 'git clone' plus maybe some more improvements like '--sparse=cone' to set up cone-mode sparse-checkout would be good. And also the implementation being contributed here is cleaner if we can use 'git clone'.
Quoted 5 lines
We are trying to balance a clean upstream implementation with some custom things that we still need in our microsoft/git fork to handle the integration with the GVFS Protocol (i.e. partial clone on Azure Repos). That customization is cleaner to keep here in the scalar code instead of adding an option to 'git clone'.
Oh, there is no disagreement on that point, at least in the short term. I was wondering why "clone" subcommand needs a duplicated logic that should be unnecessary, before realizing that this was not implemented as a wrapper to (possibly updated) "clone", and I agree that starting with a looser coupling like this step does is easier to everybody.
"Kind of shame" is just that I wished we had already prepared "git clone" side to accept customization more easily before its various distinct phases (new repository creation, where a custom logic may want to affect the name and location of it and how "git init" is driven, initial "fetch", where a custom logic may want to affect the fetch refspec and its parameters like depths and cones, and initial "checkout") do their things. If we allowed such plug-in of logic to affect how "git clone" worked already, it would have been possible to do "scalar clone" with much less code. It also would allow us to reorganize the "clone --local" hack in a way that is easier to reason about (I think even in today's code, the way I hooked it up can be seen which is quite messy). It may even help folks who want to extend "git clone" to clone a repository recursively its submodules with project-specific customizations (like which ones to clone by default, etc.).
I suspect that learning from the way "scalar clone" is done on top of "init" + "fetch" + "checkout" in this initial series may help us extend "git clone" later to fill such needs.
Quoted 3 lines
If your opinion to switch to 'git clone' is a strong one, then I could see us doing that change. I just want you to be aware of the hidden reasons for choices like these.
Not at all at this moment.
It is mostly that the way "init" + "fetch" + "checkout" was done in this step reminded me of a much longer-term wish I have had for a while.
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Johannes SchindelinQuoted 32 lines · Johannes Schindelin
It would even work if the current line is shorter, but as you point out:
it is wasteful. And it could be improved to be more readable. I reworked it, and it now looks like this:if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { const char *line = out.buf; while (*line) { const char *eol = strchrnul(line, '\n'), *p; size_t len = eol - line; char *branch; if (!skip_prefix(line, "ref: ", &p) || !strip_suffix_mem(line, &len, "\tHEAD")) { line = eol + (*eol == '\n'); continue; } eol = line + len; if (skip_prefix(p, "refs/heads/", &p)) { branch = xstrndup(p, eol - p); strbuf_release(&out); return branch; } error(_("remote HEAD is not a branch: '%.*s'"), (int)(eol - p), p); strbuf_release(&out); return NULL; } }
Quoted 3 lines
It now parses the output line by line, looking for the expected prefix and suffix, then verifies the ref name format, and either returns the short branch name or errors out with the message that this is not a branch.
It is much easier to read and understand how the loop works with above.
Quoted 12 lines
Quoted 11 lines
contrib/scalar/scalar.c static int unregister_dir(void) · line 313
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(head - ref), ref); +strbuf_release (&out); +return NULL ;OK. Any symref whose basename is HEAD in their remote-tracking hierarchy would have been rejected earlier in the loop.
Is there a particular reason why we return early here, instead of breaking out of hte loop and let the generic "failed to get" code path below to handle this case?
Quoted 5 lines
Yes, the reason is that I wanted to err on the side of caution. If the remote repository reports a default branch that is not a default branch at all, I do not want to pretend that things are fine and then run into trouble later when we set up a non-branch as remote-tracking target or something like that.
Wouldn't we have the same problem when the remote end does not advertise HEAD and we fall back to "local default", though? We'd run into trouble later as we use "local default" that may correspond to a non-branch there as remote-tracking target, or something like that.
Not that I care too deeply in the error case, though. I just felt that this early return was an uneven way to follow the principle to err on the side of caution, as we continue with the local default when the other side fails to tell us what their HEAD points at.
Thanks.
Re: [PATCH 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Johannes SchindelinQuoted 10 lines · Johannes Schindelin
Quoted 5 lines
"The design decisions we made are all part of being opinionated" can all explain it away, but at least we should let the users know where the opinionated choices scalar makes want to lead them to, and this "src/" stuff needs a bit of clarification. Perhaps a documentation will be added in later steps?
I had hoped that the initial blurb of the manual page was sufficient, but you're right, the
registersubcommand is particular in that it allows to force Scalar to consider the worktree to be identical to the Scalar enlistment. I added this:
Sorry, if it weren't clear that I was commenting on each step as I read along without peeking later steps. I think I saw it was written somewhere that this was to encourage use of read-only directory that keeps the sources with build artifacts and crufts created outside it (so forests of projects will not have the source directories, each of which has its own .git/, next to each other--- instead we would have shell directories, each with its own src/ and src/.git, next to each other). The additional documentation below is a good thing to have handy when readers learn how to use "register" (or more generally, what an "enlistment" is). As long as the motivation behind that design is given somewhere (not necessarily here) for readers to discover, I am OK with the design.
Quoted 4 lines
diff --git
a/contrib/scalar/scalar.txtb/contrib/scalar/scalar.txtindex1593da45eae..568987064b2100644
---a/contrib/scalar/scalar.txt
+++b/contrib/scalar/scalar.txt
Quoted 8 lines
@@ -40,6 +40,10 @@ register [<enlistment>]::
and starts background maintenance. If<enlistment>is not provided, then the enlistment associated with the current working directory is registered.
++
+Note: when this subcommand is called in a worktree that is calledsrc/, its +parent directory is considered to be the Scalar enlistment. If the worktree is +_not_ calledsrc/, it itself will be considered to be the Scalar enlistment.
Thanks.
Re: [PATCH 10/15] scalar: implement the `run` command
In reply to Johannes SchindelinQuoted 14 lines · Johannes Schindelin
Hi Ævar,
Quoted 5 lines · Ævar Arnfjörð Bjarmason
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 509
Quoted 1 line · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +1
-0+const char *usagestr[] = {NULL ,NULL };Missing usage strings?
This command will show a generated usage, i.e. a non-static string. It therefore cannot be specified here already. See the
strbuf_*()calls populatingbufand theusagestr[0] = buf.buf;assignment.
Quoted 6 lines
Quoted 3 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 520
Quoted 1 line
contrib/scalar/scalar.c +1
-0+if (argc ==0 )Style nit (per style guide): s/argc == 0/!argc/g.
It is true that we often do this, but in this instance it would be
misleading:argcis a counter, not a Boolean.
That argument could be a plausible excuse to deviate from the style if it were
if (argc == 0) do no args case; else if (argc == 1) do one arg case; else if (argc == 2) do two args case; ...
Replacing the first one with "if (!argc)" may make it less readable.
But I do not think the reasoning applies here
if (argc == 0) do a thing that applies only to no args case;
without "else". This is talking about "do we have any argument? Yes or no?" Boolean here.
Quoted 7 lines
Quoted 4 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 524
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+if (!strcmp ("all" , argv[0 ])) + i =-1 ;Style nit (per style guide): missing braces here.
The style guide specifically allows my preference to leave single-line blocks without curlies.
Actually, the exception goes the other way, no?
We generally want to avoid such an unnecessary braces around a single statement block. But when we have an else clause that has a block with multiple statements (hence braces are required), as an exception, the guide asks you to write braces around the body of the if side for consistency.
When you only have just a couple of lines on the "else {}" side, I do not think it matters too much either way for readability, though. I cannot see the "else" side in the above clause, but IIRC it wasn't just a few lines, was it?
Thanks.
v2 cover Upstreaming the Scalar command
In reply to Johannes Schindelin via GitGitGadgettl;dr: This series contributes the Scalar command to the Git project. This command provides an opinionated way to create and configure repositories with a focus on very large repositories.
Background
==========
Years ago, Microsoft wanted to move the source code of the Windows operating system to Git. The challenge there was to prove that Git could scale to massive monorepos. The VFS for Git (formerly GVFS) project was born to take up that challenge.
The final solution included a virtual filesystem (with both user-mode and kernel components) and a customized fork of Git for Windows. This solution contained several key concepts, such as only populating a portion of the working directory, demand-fetching blobs, and performing periodic repo maintenance in the background. However, the required kernel drivers made it difficult to port the solution to other platforms.
But it was realized that many of these key concepts were independent of the actual VFS and its projection of the working directory. The Scalar project was created to make that separation, refine the key concepts, and then extract those features into the new Scalar command.
The present
===========
The Scalar project provides a completely functional non-virtual experience for monorepos. But why stop there. The Scalar project was designed to be a self-destructing vehicle to allow those key concepts to be moved into core Git itself for the benefit of all. For example, partial clone, sparse-checkout, and background maintenance have already been upstreamed and removed from Scalar proper. This patch series provides a C-based implementation of the final remaining portions of the Scalar command. This will make it easier for users to experiment with the Scalar command. It will also make it substantially easier to experiment with moving functionality from Scalar into core Git, while maintaining backwards-compatibility for existing Scalar users.
The C-based Scalar has been shipped to Scalar users, and can be tested by any interested reader:
https://github.com/microsoft/git/releases/tag/v2.33.0.vfs.0.0 (it offers a Git for Windows installer, a macOS package and an Ubuntu package).
Opportunities
=============
Apart from providing the Scalar command, this contribution is intended to serve as a basis for further mailing list discussions on moving (some of) these key concepts into the main Git commands.
For example, we previously discussed the idea of a "git big-clone" that does much of what "scalar clone" is doing. This patch series is a step to make such functionality exist in the Git code base while we simmer on what such a "git big-clone" command-line interface would look like.
This is one of many possible ways to do this. Creating a 'git big-clone' could lock Git into backwards compatibility concerns so it is necessary to approach such an endeavor with caution. As a discussion starter, the scalar clone <url> command does roughly this:
1. git clone --sparse --filter=blob:none /src
2. git -C /src sparse-checkout init --cone
3. git -C /src config (many times)
4. git -C /src maintenance start
It is my hope inspire discussions about what parts of Scalar could go into core Git, and where, and in which form. While we wish to maintain backwards-compatibility of Scalar's command-line interface (because it is already in use), by having the Scalar code in the same code base as Git's, it will be much easier to move functionality without having to maintain loose version coupling between independently-versioned Scalar and Git. The tight version-coupling, along with having access to libgit.a also allows the C-based implementation of Scalar to be much smaller than the original .NET version.
For example, we might choose in the future to implement, say, git clone --scale=partial,cone to initialize a partial clone with a cone-sparse checkout, that would not only be totally doable, and not only would we already have precedent and data to prove that this actually makes engineers happy who have to work on ginormous repositories, but we could then also implement it by moving parts of contrib/scalar/ to builtin/ (where contrib/scalar/ would then call the built-ins accordingly rather than hard-coding the defaults itself).
We now also have the opportunity to discuss the merits of Scalar's clone caching, which is not actually part of this patch series because it is a bit coupled with the GVFS parts of microsoft/git for the moment, where clones automatically get registered with a populated alternate repository that is identified by the URL, meaning: subsequent clones of the same repository are vastly faster than the first one because they do not actually download the already-received objects again, they access the cache instead.
Another thing that I could imagine to be discussed at length is the distinction between enlistment and worktree (where the latter is the actual Git worktree and usually lives in the src/ subdirectory of the former). This encourages untracked and ignored files to be placed outside the worktree, making Git's job much easier. This idea, too, might find its way in one way or another into Git proper.
These are just a few concepts in Scalar that do not yet have equivalents in Git. By putting this initial implementation into contrib/, we create a foundation for future discussions of these concepts.
We plan on updating the recommended config settings in scalar register as new Git features are available (such as builtin FSMonitor and sparse-index, when ready). To facilitate upgrading existing Scalar enlistments, their paths are automatically added to the [scalar] section of the global Git config, and the scalar reconfigure --all command will process all of them.
Epilogue
========
Now, to address some questions that I imagine every reader has who made it this far:
* Why not put the Scalar functionality directly into a built-in? Creating a Git builtin requires scrutiny over every aspect of the feature, which is difficult to do while also maintaining the command-line interface contract and expected behavior of the Scalar command (there are existing users, after all). By having the Scalar command in contrib/, we present a simple option for users to have these features in the short term while the Git contributor community decides which bits to absorb into Git built-ins.
* Why implement the Scalar command in the Git codebase? We ported Scalar to the microsoft/git fork for several reasons. First, we realized it was possible now that the core features exist inside Git itself. Second, compiling Scalar directly within a version of Git allows us to remove a version compatibility check from each config option that might or might not apply based on the installed Git version. Finally, this new location has greatly simplified our release process and the installation process for users. We now have ways to install Scalar with microsoft/git via winget, brew, and apt-get. This has been the case since we shipped v2.32.0 to our users, read: this setup has served us well already.
* Why contribute Scalar to the Git project? We are biased, of course, yet we do have evidence that the Scalar command is a helpful tool that offers an simple way to handle huge repositories with ease. By contributing it to the core Git project, we are able to share it with more users, especially some users who do not want to install the microsoft/git fork. We intend to include Scalar as a component in git-for-windows/git, but are contributing it here first. Further, we think there is benefit to the Git developer community as this presents an example of how to set certain defaults that work for large repositories.
* Does this integrate with the built-in FSMonitor yet? No, not yet. I do have a couple of add-on patch series lined up, one of them being the integration with the built-in FSMonitor, which obviously has to wait until the FSMonitor patch series advances further.
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with Scalar
Derrick Stolee (4):
scalar: 'register' sets recommended config and starts maintenance
scalar: 'unregister' stops background maintenance
scalar: implement 'scalar list'
scalar: implement the run command
Johannes Schindelin (10):
scalar: create a rudimentary executable
scalar: start documenting the command
scalar: create test infrastructure
scalar: let 'unregister' handle a deleted enlistment directory
gracefully
scalar: implement the clone subcommand
scalar: teach 'clone' to support the --single-branch option
scalar: allow reconfiguring an existing enlistment
scalar: teach 'reconfigure' to optionally handle all registered
enlistments
scalar: implement the version command
scalar: accept -C and -c options before the subcommand
Matthew John Cheetham (1):
scalar: implement the delete command
Makefile | 8 + contrib/scalar/.gitignore | 5 + contrib/scalar/Makefile | 57 +++ contrib/scalar/scalar.c | 844 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 156 ++++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 ++++ 7 files changed, 1236 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c create mode 100644 contrib/scalar/scalar.txt create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
base-commit: ebf3c04b262aa27fbb97f8a0156c2347fecafafb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1005%2Fdscho%2Fscalar-the-beginning-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v1:
1: b8c7d3f8450 = 1: b8c7d3f8450 scalar: create a rudimentary executable
2: 4f886575dcf = 2: 4f886575dcf scalar: start documenting the command
3: bcfde9bc765 = 3: bcfde9bc765 scalar: create test infrastructure
4: 3786f4c597f ! 4: ee3e26a0c4e scalar: 'register' sets recommended config and starts maintenance
@@ contrib/scalar/scalar.c
+ */
+ { "core.untrackedCache", "false" },
+#endif
-+ { "core.bare", "false" },
+ { "core.logAllRefUpdates", "true" },
+ { "credential.https://dev.azure.com.useHttpPath", "true" },
+ { "credential.validate", "false" }, /* GCM4W-only */
@@ contrib/scalar/scalar.txt: will be identical to the worktree.
+~~~~~~~~
+
+register [<enlistment>]::
-+ Adds the enlistment's repository to the list of registered repositories
-+ and starts background maintenance. If `<enlistment>` is not provided,
-+ then the enlistment associated with the current working directory is
-+ registered.
++ Adds the enlistment's repository to the list of registered repositories
++ and starts background maintenance. If `<enlistment>` is not provided,
++ then the enlistment associated with the current working directory is
++ registered.
+++
++Note: when this subcommand is called in a worktree that is called `src/`, its
++parent directory is considered to be the Scalar enlistment. If the worktree is
++_not_ called `src/`, it itself will be considered to be the Scalar enlistment.
+
SEE ALSO
-------- 5: 2a6ac170e6b ! 5: 6142f75875b scalar: 'unregister' stops background maintenance
@@ contrib/scalar/scalar.txt: SYNOPSIS
DESCRIPTION
-----------
-@@ contrib/scalar/scalar.txt: register [<enlistment>]::
- then the enlistment associated with the current working directory is
- registered.
+@@ contrib/scalar/scalar.txt: Note: when this subcommand is called in a worktree that is called src/, its
+ parent directory is considered to be the Scalar enlistment. If the worktree is
+ _not_ called src/, it itself will be considered to be the Scalar enlistment.
+Unregister
+~~~~~~~~~~
+
+unregister [<enlistment>]::
-+ Remove the specified repository from the list of repositories
-+ registered with Scalar and stop the scheduled background maintenance.
++ Remove the specified repository from the list of repositories
++ registered with Scalar and stop the scheduled background maintenance.
+
SEE ALSO
--------
6: 087fc9be194 = 6: 82dd253154f scalar: let 'unregister' handle a deleted enlistment directory gracefully
7: c272ff4069d ! 7: fb7c931ddb3 scalar: implement 'scalar list'
@@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
+~~~~
+
+list::
-+ To see which repositories are currently registered by the service, run
-+ `scalar list`. This subcommand does not need to be run inside a Scalar
-+ enlistment.
++ To see which repositories are currently registered by the service, run
++ `scalar list`. This subcommand does not need to be run inside a Scalar
++ enlistment.
+
Register
~~~~~~~~
8: 2cbf0b61113 ! 8: f3223c10788 scalar: implement the `clone` subcommand
@@ Commit message
experience and experiments of the Microsoft Windows and the Microsoft
Office development teams.
- Note: We intentionally use a slightly wasteful `set_config()` function
- (which does not reuse a single `strbuf`, for example, though performance
- _really_ does not matter here) for convenience and readability.
-
- Also note: since the `scalar clone` command is by far the most commonly
+ Note: since the `scalar clone` command is by far the most commonly
called `scalar` subcommand, we document it at the top of the manual
page.
@@ contrib/scalar/scalar.c: static int unregister_dir(void)
+
+ cp.git_cmd = 1;
+ strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL);
-+ strbuf_addstr(&out, "-\n");
+ if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
-+ char *ref = out.buf;
-+
-+ while ((ref = strstr(ref + 1, "\nref: "))) {
-+ const char *p;
-+ char *head, *branch;
++ const char *line = out.buf;
+
-+ ref += strlen("\nref: ");
-+ head = strstr(ref, "\tHEAD");
++ while (*line) {
++ const char *eol = strchrnul(line, '\n'), *p;
++ size_t len = eol - line;
++ char *branch;
+
-+ if (!head || memchr(ref, '\n', head - ref))
++ if (!skip_prefix(line, "ref: ", &p) ||
++ !strip_suffix_mem(line, &len, "\tHEAD")) {
++ line = eol + (*eol == '\n');
+ continue;
++ }
+
-+ if (skip_prefix(ref, "refs/heads/", &p)) {
-+ branch = xstrndup(p, head - p);
++ eol = line + len;
++ if (skip_prefix(p, "refs/heads/", &p)) {
++ branch = xstrndup(p, eol - p);
+ strbuf_release(&out);
+ return branch;
+ }
+
+ error(_("remote HEAD is not a branch: '%.*s'"),
-+ (int)(head - ref), ref);
++ (int)(eol - p), p);
+ strbuf_release(&out);
+ return NULL;
+ }
@@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
+~~~~~
+
+clone [<options>] <url> [<enlistment>]::
-+ Clones the specified repository, similar to linkgit:git-clone[1]. By
-+ default, only commit and tree objects are cloned. Once finished, the
-+ worktree is located at `<enlistment>/src`.
++ Clones the specified repository, similar to linkgit:git-clone[1]. By
++ default, only commit and tree objects are cloned. Once finished, the
++ worktree is located at `<enlistment>/src`.
++
+The sparse-checkout feature is enabled (except when run with `--full-clone`)
+and the only files present are those in the top-level directory. Use
@@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
+
+-b <name>::
+--branch <name>::
-+ Instead of checking out the branch pointed to by the cloned repository's
-+ HEAD, check out the `<name>` branch instead.
++ Instead of checking out the branch pointed to by the cloned
++ repository's HEAD, check out the `<name>` branch instead.
+
+--[no-]full-clone::
-+ A sparse-checkout is initialized by default. This behavior can be turned
-+ off via `--full-clone`.
++ A sparse-checkout is initialized by default. This behavior can be
++ turned off via `--full-clone`.
+
List
~~~~
list::
- To see which repositories are currently registered by the service, run
-- `scalar list`. This subcommand does not need to be run inside a Scalar
-- enlistment.
-+ `scalar list`. This subcommand, like `clone`, does not need to be run
-+ inside a Scalar enlistment.
+ To see which repositories are currently registered by the service, run
+- `scalar list`. This subcommand does not need to be run inside a Scalar
+- enlistment.
++ `scalar list`. This subcommand, like `clone`, does not need to be run
++ inside a Scalar enlistment.
Register
~~~~~~~~ 9: 9af1c37c2ea ! 9: b3c4b3dccc6 scalar: teach 'clone' to support the --single-branch option
@@ contrib/scalar/scalar.txt: scalar - an opinionated repository management tool
scalar register [<enlistment>]
scalar unregister [<enlistment>]
@@ contrib/scalar/scalar.txt: subdirectories outside your sparse-checkout by using git ls-tree HEAD.
- Instead of checking out the branch pointed to by the cloned repository's
- HEAD, check out the <name> branch instead.
+ Instead of checking out the branch pointed to by the cloned
+ repository's HEAD, check out the <name> branch instead.
+--[no-]single-branch::
-+ Clone only the history leading to the tip of a single branch,
-+ either specified by the --branch option or the primary
-+ branch remote's HEAD points at.
++ Clone only the history leading to the tip of a single branch, either
++ specified by the --branch option or the primary branch remote's
++ HEAD points at.
++
+Further fetches into the resulting repository will only update the +remote-tracking branch for the branch this option was used for the initial
@@ contrib/scalar/scalar.txt: subdirectories outside your sparse-checkout by using
+--single-branch clone was made, no remote-tracking branch is created. +
--[no-]full-clone::
- A sparse-checkout is initialized by default. This behavior can be turned
- off via --full-clone.
+ A sparse-checkout is initialized by default. This behavior can be
+ turned off via --full-clone.
## contrib/scalar/t/t9099-scalar.sh ##
@@ contrib/scalar/t/t9099-scalar.sh: test_expect_success 'set up repository to clone' '
10: c3f16bccd02 ! 10: b7fc2dc29c8 scalar: implement the run command
@@ contrib/scalar/scalar.txt: scalar clone [--single-branch] [--branch <main-branch
DESCRIPTION
-----------
@@ contrib/scalar/scalar.txt: unregister [<enlistment>]::
- Remove the specified repository from the list of repositories
- registered with Scalar and stop the scheduled background maintenance.
+ Remove the specified repository from the list of repositories
+ registered with Scalar and stop the scheduled background maintenance.
+Run
+~~~
+
+scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]::
-+ Run the given maintenance task (or all tasks, if all was specified).
-+ Except for all and config, this subcommand simply hands off to
-+ linkgit:git-maintenance[1] (mapping fetch to prefetch and
-+ pack-files to incremental-repack).
++ Run the given maintenance task (or all tasks, if all was specified).
++ Except for all and config, this subcommand simply hands off to
++ linkgit:git-maintenance[1] (mapping fetch to prefetch and
++ pack-files to incremental-repack).
++
+These tasks are run automatically as part of the scheduled maintenance,
+as soon as the repository is registered with Scalar. It should therefore
11: 13056f02018 ! 11: 9a834c23d08 scalar: allow reconfiguring an existing enlistment
@@ contrib/scalar/scalar.c: static int set_recommended_config(void)
- { "core.untrackedCache", "false" },
+ { "core.untrackedCache", "false", 1 },
#endif
-- { "core.bare", "false" },
- { "core.logAllRefUpdates", "true" },
- { "credential.https://dev.azure.com.useHttpPath", "true" },
- { "credential.validate", "false" }, /* GCM4W-only */
@@ contrib/scalar/scalar.c: static int set_recommended_config(void)
- { "feature.experimental", "false" },
- { "fetch.unpackLimit", "1" },
- { "fetch.writeCommitGraph", "false" },
-+ { "core.bare", "false", 1 },
+ { "core.logAllRefUpdates", "true", 1 },
+ { "credential.https://dev.azure.com.useHttpPath", "true", 1 },
+ { "credential.validate", "false", 1 }, /* GCM4W-only */ 12: 732a28c22fc ! 12: 79e9f5d203a scalar: teach 'reconfigure' to optionally handle all registered enlistments
@@ contrib/scalar/scalar.txt: After a Scalar upgrade, or when the configuration of
reconfigure the enlistment.
+With the --all option, all enlistments currently registered with Scalar
-+will be reconfigured. This option is meant to to be run every time Scalar
-+was upgraded.
++will be reconfigured. This option is meant to to be run every time after
++Scalar is upgraded.
+
SEE ALSO
--------
13: 13afbd68812 ! 13: 94a21982652 scalar: implement the delete command
@@ contrib/scalar/scalar.txt: scalar register [<enlistment>]
DESCRIPTION
-----------
@@ contrib/scalar/scalar.txt: With the --all option, all enlistments currently registered with Scalar
- will be reconfigured. This option is meant to to be run every time Scalar
- was upgraded.
+ will be reconfigured. This option is meant to to be run every time after
+ Scalar is upgraded.
+Delete
+~~~~~~
+
+delete <enlistment>::
-+ This subcommand lets you delete an existing Scalar enlistment from your
-+ local file system, unregistering the repository.
++ This subcommand lets you delete an existing Scalar enlistment from your
++ local file system, unregistering the repository.
+
SEE ALSO
--------
14: 73d08c0c894 ! 14: 707d8e19683 scalar: implement the version command
@@ Commit message
Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as
- git versions. For backwards-compatibility with the .NET version,
+ git version. For backwards-compatibility with the .NET version,
scalar version prints to stderr, though (git version prints to stdout instead).
15: 6455b18f1b6 ! 15: 26e23b5c5e5 scalar: accept -C and -c options before the subcommand
@@ contrib/scalar/scalar.txt: The scalar command implements various subcommands,
+The following options can be specified _before_ the subcommand:
+
+-C <directory>::
-+ Before running the subcommand, change the working directory. This
-+ option imitates the same option of linkgit:git[1].
++ Before running the subcommand, change the working directory. This
++ option imitates the same option of linkgit:git[1].
+
+-c <key>=<value>::
-+ For the duration of running the specified subcommand, configure this
-+ setting. This option imitates the same option of linkgit:git[1].
++ For the duration of running the specified subcommand, configure this
++ setting. This option imitates the same option of linkgit:git[1].
+
COMMANDS
--------
Signature · 1 line
gitgitgadget
PATCH v2 1/15 scalar: create a rudimentary executable
From: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
With this patch, we start the journey from that C# project to move what is left to Git's own contrib/ directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It was always to plan to contribute all of the proven strategies back to core Git.
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
With partial clone, sparse checkout and git maintenance having been upstreamed, there is little left that scalar.exe does that which git.exe cannot do. One such thing is that scalar clone <url> will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the scalar executable and put it into the contrib/scalar/ subdirectory.
The slightly awkward addition of the contrib/scalar/* bits to the top-level Makefile are actually really required: we want to link to libgit.a, which means that we will need to use the very same CFLAGS and LDFLAGS as the rest of Git.
An early development version of this patch tried to replicate all the conditional code in contrib/scalar/Makefile (e.g. NO_POLL) just like contrib/svn-fe/Makefile used to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disabling compat/poll/, compat/regex/, compat/win32mmap.c & friends depending on the current platform... To put it mildly: it was a major mess.
Instead, this patch makes minimal changes to the top-level Makefile so that the bits in contrib/scalar/ can be compiled and linked, and adds a contrib/scalar/Makefile that uses the top-level Makefile in a most minimal way to do the actual compiling.
Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +8 -0
@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
contrib/scalar/.gitignore new file +2 -0
@@ -0,0 +1,2 @@ +/*.exe +/scalar
contrib/scalar/Makefile new file +34 -0
@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCE
contrib/scalar/scalar.c new file +36 -0
@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +}
Signature · 1 line
gitgitgadget
PATCH v2 3/15 scalar: create test infrastructure
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
To test the Scalar command, create a test script in contrib/scalar/t that is executed as make -C contrib/scalar test. Since Scalar has no meaningful capabilities yet, the only test is rather simple. We will add more tests in subsequent commits that introduce corresponding, new functionality.
Note: this test script is intended to test scalar only lightly, even after all of the functionality is implemented.
A more comprehensive functional (or: integration) test suite can be found at https://github.com/microsoft/scalar; It is used in the workflow
https://github.com/microsoft/git/blob/HEAD/.github/workflows/scalar-functional-tests.yml
in Microsoft's Git fork. This test suite performs end-to-end tests with a real remote repository, and is run as part of the regular CI builds. Since those tests require some functionality supported only by Microsoft's Git fork ("GVFS protocol"), there is no intention to port that fuller test suite to contrib/scalar/.
--- contrib/scalar/Makefile | 17 +++++-- contrib/scalar/t/Makefile | 78 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 17 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
contrib/scalar/Makefile +14 -3
@@ -3,6 +3,7 @@ QUIET_SUBDIR1 = ifneq ($(findstring s,$(MAKEFLAGS)),s) ifndef V + QUIET_GEN = @echo ' ' GEN $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -21,7 +22,7 @@ include ../../config.mak.uname TARGETS = scalar$(X) scalar.o GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a -all: scalar$X +all: scalar$X ../../bin-wrappers/scalar $(GITLIBS): $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ -30,9 +31,19 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar $(RM) scalar.1 scalar.html scalar.xml +../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile + @mkdir -p ../../bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell cd ../.. && pwd)|' \ + -e 's|@@PROG@@|contrib/scalar/scalar$(X)|' < $< > $@ && \ + chmod +x $@ + +test: all + $(MAKE) -C t + docs: scalar.html scalar.1 scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
@@ -43,4 +54,4 @@ scalar.html scalar.1: scalar.txt ../contrib/scalar/$@ $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . -.PHONY: all clean docs FORCE +.PHONY: all clean docs test FORCE
contrib/scalar/t/Makefile new file +78 -0
78 lines not on this page · raw message
contrib/scalar/t/t9099-scalar.sh new file +17 -0
@@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='test the `scalar` command' + +TEST_DIRECTORY=$PWD/../../../t +export TEST_DIRECTORY + +# Make it work with --no-bin-wrappers +PATH=$PWD/..:$PATH + +. ../../../t/test-lib.sh + +test_expect_success 'scalar shows a usage' ' + test_expect_code 129 scalar -h +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v2 2/15 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This commit establishes the infrastructure to build the manual page for the scalar command.
--- contrib/scalar/.gitignore | 3 +++ contrib/scalar/Makefile | 14 +++++++++++++- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 contrib/scalar/scalar.txt
contrib/scalar/.gitignore +3 -0
@@ -1,2 +1,5 @@ +/*.xml +/*.1 +/*.html /*.exe /scalar
contrib/scalar/Makefile +13 -1
@@ -6,6 +6,7 @@ ifndef V QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir + QUIET = @ else export V endif
@@ -30,5 +31,16 @@ $(TARGETS): $(GITLIBS) scalar.c clean: $(RM) $(TARGETS) + $(RM) scalar.1 scalar.html scalar.xml -.PHONY: all clean FORCE +docs: scalar.html scalar.1 + +scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel + +scalar.html scalar.1: scalar.txt + $(QUIET_SUBDIR0)../../Documentation$(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/scalar/scalar.txt \ + ../contrib/scalar/$@ + $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . + +.PHONY: all clean docs FORCE
contrib/scalar/scalar.txt new file +38 -0
@@ -0,0 +1,38 @@ +scalar(1) +========= + +NAME +---- +scalar - an opinionated repository management tool + +SYNOPSIS +-------- +[verse] +scalar <command> [<options>] + +DESCRIPTION +----------- + +Scalar is an opinionated repository management tool. By creating new +repositories or registering existing repositories with Scalar, your Git +experience will speed up. Scalar sets advanced Git config settings, +maintains your repositories in the background, and helps reduce data sent +across the network. + +An important Scalar concept is the enlistment: this is the top-level directory +of the project. It usually contains the subdirectory `src/` which is a Git +worktree. This encourages the separation between tracked files (inside `src/`) +and untracked files, such as build artifacts (outside `src/`). When registering +an existing Git worktree with Scalar whose name is not `src`, the enlistment +will be identical to the worktree. + +The `scalar` command implements various subcommands, and different options +depending on the subcommand. + +SEE ALSO +-------- +linkgit:git-maintenance[1]. + +Scalar +--- +Associated with the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
PATCH v2 4/15 scalar: 'register' sets recommended config and starts maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Let's start implementing the register command. With this commit, recommended settings are configured upon scalar register, and Git's background maintenance is started.
The recommended config settings may very well change in the future. For example, once the built-in FSMonitor is available, we will want to enable it upon scalar register. For that reason, we explicitly support running scalar register in an already-registered enlistment.
--- contrib/scalar/scalar.c | 255 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 272 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +255 -0
@@ -5,11 +5,266 @@#include "cache.h" #include "gettext.h" #include "parse-options.h" +#include "config.h" +#include "run-command.h" + +/* +* Remove the deepest subdirectory in the provided path string. Path must not +* include a trailing path separator. Returns 1 if parent directory found, +* otherwise 0. +*/ +static int strbuf_parent_directory (struct strbuf *buf) +{ +size_t len = buf->len; +size_t offset =offset_1st_component (buf->buf); +char *path_sep =find_last_dir_sep (buf->buf + offset); +strbuf_setlen (buf, path_sep ? path_sep - buf->buf : offset); + +return buf->len < len; +} + +static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); +do { +const size_t len = path.len; + +/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + } + +/* reset to original path */ +strbuf_setlen (&path, len); + +/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) { +strbuf_setlen (&path, len); + +if (enlistment_root) { +/* +* If the worktree's directory's name is `src`, the enlistment is the +* parent directory, otherwise it is identical to the worktree. +*/ + root =strip_path_suffix (path.buf,"src" ); +strbuf_addstr (enlistment_root, root ? root : path.buf); +free (root); + } + + enlistment_found =1 ; +break ; + } + +strbuf_setlen (&path, len); + }while (strbuf_parent_directory (&path)); + +if (!enlistment_found) +die (_ ("could not find enlistment root" )); + +if (chdir (path.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), path.buf); + +strbuf_release (&path); +setup_git_directory (); +} + +static int run_git (const char *arg, ...) +{ +struct strvec argv =STRVEC_INIT ; +va_list args; +const char *p; +int res; + +va_start (args, arg); +strvec_push (&argv, arg); + while ((p = va_arg(args, const char *))) +strvec_push (&argv, p); +va_end (args); + + res =run_command_v_opt (argv.v,RUN_GIT_CMD ); + +strvec_clear (&argv); +return res; +} + +static int set_recommended_config (void ) +{ +struct { +const char *key; +const char *value; + } config[] = { + {"am.keepCR" ,"true" }, + {"core.FSCache" ,"true" }, + {"core.multiPackIndex" ,"true" }, + {"core.preloadIndex" ,"true" }, +#ifndef WIN32 + {"core.untrackedCache" ,"true" }, +#else +/* +* Unfortunately, Scalar's Functional Tests demonstrated +* that the untracked cache feature is unreliable on Windows +* (which is a bummer because that platform would benefit the +* most from it). For some reason, freshly created files seem +* not to update the directory's `lastModified` time +* immediately, but the untracked cache would need to rely on +* that. +* +* Therefore, with a sad heart, we disable this very useful +* feature on Windows. +*/ + {"core.untrackedCache" ,"false" }, +#endif + {"core.logAllRefUpdates" ,"true" }, + {"credential.https://dev.azure.com.useHttpPath" ,"true" }, + {"credential.validate" ,"false" },/* GCM4W-only */ + {"gc.auto" ,"0" }, + {"gui.GCWarning" ,"false" }, + {"index.threads" ,"true" }, + {"index.version" ,"4" }, + {"merge.stat" ,"false" }, + {"merge.renames" ,"false" }, + {"pack.useBitmaps" ,"false" }, + {"pack.useSparse" ,"true" }, + {"receive.autoGC" ,"false" }, + {"reset.quiet" ,"true" }, + {"feature.manyFiles" ,"false" }, + {"feature.experimental" ,"false" }, + {"fetch.unpackLimit" ,"1" }, + {"fetch.writeCommitGraph" ,"false" }, +#ifdef WIN32 + {"http.sslBackend" ,"schannel" }, +#endif + {"status.aheadBehind" ,"false" }, + {"commitGraph.generationVersion" ,"1" }, + {"core.autoCRLF" ,"false" }, + {"core.safeCRLF" ,"false" }, + {NULL ,NULL }, + }; +int i; +char *value; + +for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + } + } + +/* +* The `log.excludeDecoration` setting is special because it allows +* for multiple values. +*/ +if (git_config_get_string ("log.excludeDecoration" , &value)) { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"created" ); +if (git_config_set_multivar_gently ("log.excludeDecoration" , +"refs/prefetch/*" , +CONFIG_REGEX_NONE ,0 )) +return error (_ ("could not configure " +"log.excludeDecoration" )); + }else { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"exists" ); +free (value); + } + +return 0 ; +} + +static int start_maintenance (void ) +{ +return run_git ("maintenance" ,"start" ,NULL ); +} + +static int add_enlistment (void ) +{ +int res; + +if (!the_repository->worktree) +die (_ ("Scalar enlistments require a worktree" )); + + res =run_git ("config" ,"--global" ,"--get" ,"--fixed-value" , +"scalar.repo" , the_repository->worktree,NULL ); + +/* +* If the setting is already there, then do nothing. +*/ +if (!res) +return 0 ; + +return run_git ("config" ,"--global" ,"--add" , +"scalar.repo" , the_repository->worktree,NULL ); +} + +static int register_dir (void ) +{ +int res =add_enlistment (); + +if (!res) + res =set_recommended_config (); + +if (!res) + res =start_maintenance (); + +return res; +} + +static int cmd_register (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar register [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return register_dir (); +}static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + {"register" , cmd_register }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +17 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar <command> [<options>] +scalar register [<enlistment>] DESCRIPTION -----------
@@ -29,6 +29,22 @@ will be identical to the worktree. The `scalar` command implements various subcommands, and different options depending on the subcommand. +COMMANDS +-------- + +Register +~~~~~~~~ + +register [<enlistment>]:: + Adds the enlistment's repository to the list of registered repositories + and starts background maintenance. If `<enlistment>` is not provided, + then the enlistment associated with the current working directory is + registered. ++ +Note: when this subcommand is called in a worktree that is called `src/`, its +parent directory is considered to be the Scalar enlistment. If the worktree is +_not_ called `src/`, it itself will be considered to be the Scalar enlistment. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v2 5/15 scalar: 'unregister' stops background maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Just like scalar register starts the scheduled background maintenance, scalar unregister stops it. Note that we use git maintenance start in scalar register, but we do not use git maintenance stop in scalar unregister: this would stop maintenance for _all_ repositories, not just for the one we want to unregister.
The unregister command also removes the corresponding entry from the [scalar] section in the global Git config.
--- contrib/scalar/scalar.c | 50 ++++++++++++++++++++++++++++++++------- contrib/scalar/scalar.txt | 8 +++++++ 2 files changed, 50 insertions(+), 8 deletions(-)
contrib/scalar/scalar.c +42 -8
@@ -204,12 +204,12 @@ static int set_recommended_config(void)return 0 ; } -static int start_maintenance (void ) +static int toggle_maintenance (int enable) { -return run_git ("maintenance" ,"start" ,NULL ); +return run_git ("maintenance" , enable ?"start" :"unregister" ,NULL ); } -static int add_enlistment (void ) +static int add_or_remove_enlistment (int add) {int res;
@@ -220,24 +220,39 @@ static int add_enlistment(void) "scalar.repo", the_repository->worktree, NULL);/* -* If the setting is already there, then do nothing. +* If we want to add and the setting is already there, then do nothing. +* If we want to remove and the setting is not there, then do nothing. */ -if (!res) +if ((add && !res) || (!add && res))return 0 ; -return run_git ("config" ,"--global" ,"--add" , +return run_git ("config" ,"--global" , add ?"--add" :"--unset" , + add ?"--no-fixed-value" :"--fixed-value" ,"scalar.repo" , the_repository->worktree,NULL ); }static int register_dir (void ) { -int res =add_enlistment (); +int res =add_or_remove_enlistment (1 );if (!res) res =set_recommended_config ();if (!res) - res =start_maintenance (); + res =toggle_maintenance (1 ); + +return res; +} + +static int unregister_dir (void ) +{ +int res =0 ; + +if (toggle_maintenance (0 ) <0 ) + res =-1 ; + +if (add_or_remove_enlistment (0 ) <0 ) + res =-1 ;return res; }
@@ -260,11 +275,30 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_unregister (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar unregister [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return unregister_dir (); +} +static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { {"register" , cmd_register }, + {"unregister" , cmd_unregister }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +8 -0
@@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] scalar register [<enlistment>] +scalar unregister [<enlistment>] DESCRIPTION -----------
@@ -45,6 +46,13 @@ Note: when this subcommand is called in a worktree that is called `src/`, its parent directory is considered to be the Scalar enlistment. If the worktree is _not_ called `src/`, it itself will be considered to be the Scalar enlistment. +Unregister +~~~~~~~~~~ + +unregister [<enlistment>]:: + Remove the specified repository from the list of repositories + registered with Scalar and stop the scheduled background maintenance. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v2 6/15 scalar: let 'unregister' handle a deleted enlistment directory gracefully
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
When a user deleted an enlistment manually, let's be generous and _still_ unregister it.
--- contrib/scalar/scalar.c | 46 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 15 +++++++++++ 2 files changed, 61 insertions(+)
contrib/scalar/scalar.c +46 -0
@@ -275,6 +275,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int remove_deleted_enlistment (struct strbuf *path) +{ +int res =0 ; +strbuf_realpath_forgiving (path, path->buf,1 ); + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"scalar.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"maintenance.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +return res; +} +static int cmd_unregister (int argc,const char **argv) {struct option options[] = {
@@ -288,6 +306,34 @@ static int cmd_unregister(int argc, const char **argv) argc =parse_options (argc, argv,NULL , options, usage,0 ); +/* +* Be forgiving when the enlistment or worktree does not even exist any +* longer; This can be the case if a user deleted the worktree by +* mistake and _still_ wants to unregister the thing. +*/ +if (argc ==1 ) { +struct strbuf src_path =STRBUF_INIT , workdir_path =STRBUF_INIT ; + +strbuf_addf (&src_path,"%s/src/.git" , argv[0 ]); +strbuf_addf (&workdir_path,"%s/.git" , argv[0 ]); +if (!is_directory (src_path.buf) && !is_directory (workdir_path.buf)) { +/* remove possible matching registrations */ +int res =-1 ; + +strbuf_strip_suffix (&src_path,"/.git" ); + res =remove_deleted_enlistment (&src_path) && res; + +strbuf_strip_suffix (&workdir_path,"/.git" ); + res =remove_deleted_enlistment (&workdir_path) && res; + +strbuf_release (&src_path); +strbuf_release (&workdir_path); +return res; + } +strbuf_release (&src_path); +strbuf_release (&workdir_path); + } +setup_enlistment_directory (argc, argv, usage, options,NULL );return unregister_dir ();
contrib/scalar/t/t9099-scalar.sh +15 -0
@@ -14,4 +14,19 @@ test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h ' +test_expect_success 'scalar unregister' ' + git init vanish/src && + scalar register vanish/src && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/vanish/src" scalar.repos && + rm -rf vanish/src/.git && + scalar unregister vanish && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/vanish/src" scalar.repos +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 7/15 scalar: implement 'scalar list'
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The produced list simply consists of those repositories registered under the multi-valued scalar.repo config setting in the user's Git config.
--- contrib/scalar/scalar.c | 11 +++++++++++ contrib/scalar/scalar.txt | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +11 -0
@@ -257,6 +257,16 @@ static int unregister_dir(void)return res; } +static int cmd_list (int argc,const char **argv) +{ +if (argc !=1 ) +die (_ ("`scalar list` does not take arguments" )); + +if (run_git ("config" ,"--global" ,"--get-all" ,"scalar.repo" ,NULL ) <0 ) +return -1 ; +return 0 ; +} +static int cmd_register (int argc,const char **argv) {struct option options[] = {
@@ -343,6 +353,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, { NULL, NULL},
contrib/scalar/scalar.txt +11 -1
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -28,11 +29,20 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. +depending on the subcommand. With the exception of `list`, all subcommands +expect to be run in an enlistment. COMMANDS -------- +List +~~~~ + +list:: + To see which repositories are currently registered by the service, run + `scalar list`. This subcommand does not need to be run inside a Scalar + enlistment. + Register ~~~~~~~~
Signature · 1 line
gitgitgadget
PATCH v2 8/15 scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated clone command: it tries to use a partial clone and sets up a sparse checkout by default. In contrast to git clone, scalar clone sets up the worktree in the src/ subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Also, it registers the repository for regular, scheduled maintenance, and configures a flurry of configuration settings based on the experience and experiments of the Microsoft Windows and the Microsoft Office development teams.
Note: since the scalar clone command is by far the most commonly called scalar subcommand, we document it at the top of the manual page.
--- contrib/scalar/scalar.c | 201 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 35 +++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 263 insertions(+), 5 deletions(-)
contrib/scalar/scalar.c +201 -0
@@ -7,6 +7,7 @@#include "parse-options.h" #include "config.h" #include "run-command.h" +#include "refs.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -257,6 +258,205 @@ static int unregister_dir(void)return res; } +/* printf-style interface, expects `<key>=<value>` argument */ +static int set_config (const char *fmt, ...) +{ +struct strbuf buf =STRBUF_INIT ; +char *value; +int res; +va_list args; + +va_start (args, fmt); +strbuf_vaddf (&buf, fmt, args); +va_end (args); + + value =strchr (buf.buf,'=' ); +if (value) + *(value++) ='\0' ; + res =git_config_set_gently (buf.buf, value); +strbuf_release (&buf); + +return res; +} + +static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +const char *line = out.buf; + +while (*line) { +const char *eol =strchrnul (line,'\n' ), *p; +size_t len = eol - line; +char *branch; + +if (!skip_prefix (line,"ref: " , &p) || + !strip_suffix_mem (line, &len,"\tHEAD" )) { + line = eol + (*eol =='\n' ); +continue ; + } + + eol = line + len; +if (skip_prefix (p,"refs/heads/" , &p)) { + branch =xstrndup (p, eol - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(eol - p), p); +strbuf_release (&out); +return NULL ; + } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} + +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b); + } + +if ((res =run_git ("-c" , buf.buf,"init" ,"--" , dir,NULL ))) + goto cleanup; + +if (chdir (dir) <0 ) { + res =error_errno (_ ("could not switch to '%s'" ), dir); + goto cleanup; + } + +setup_git_directory (); + +/* common-main already logs `argv` */ +trace2_def_repo (the_repository); + +if (!branch && !(branch =remote_default_branch (url))) { + res =error (_ ("failed to get default branch for '%s'" ), url); + goto cleanup; + } + +if (set_config ("remote.origin.url=%s" , url) || +set_config ("remote.origin.fetch=" +"+refs/heads/*:refs/remotes/origin/*" ) || +set_config ("remote.origin.promisor=true" ) || +set_config ("remote.origin.partialCloneFilter=blob:none" )) { + res =error (_ ("could not configure remote in '%s'" ), dir); + goto cleanup; + } + +if (!full_clone && + (res =run_git ("sparse-checkout" ,"init" ,"--cone" ,NULL ))) + goto cleanup; + +if (set_recommended_config ()) +return error (_ ("could not configure '%s'" ), dir); + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) { +warning (_ ("partial clone failed; attempting full clone" )); + +if (set_config ("remote.origin.promisor" ) || +set_config ("remote.origin.partialCloneFilter" )) { + res =error (_ ("could not configure for full clone" )); + goto cleanup; + } + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) + goto cleanup; + } + +if ((res =set_config ("branch.%s.remote=origin" , branch))) + goto cleanup; +if ((res =set_config ("branch.%s.merge=refs/heads/%s" , + branch, branch))) + goto cleanup; + +strbuf_reset (&buf); +strbuf_addf (&buf,"origin/%s" , branch); + res =run_git ("checkout" ,"-f" ,"-t" , buf.buf,NULL ); +if (res) + goto cleanup; + + res =register_dir (); + +cleanup: +free (enlistment); +free (dir); +strbuf_release (&buf); +return res; +} +static int cmd_list (int argc,const char **argv) {if (argc !=1 )
@@ -353,6 +553,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "clone", cmd_clone }, { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister },
contrib/scalar/scalar.txt +30 -5
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -29,19 +30,43 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `list`, all subcommands -expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone` and `list`, all +subcommands expect to be run in an enlistment. COMMANDS -------- +Clone +~~~~~ + +clone [<options>] <url> [<enlistment>]:: + Clones the specified repository, similar to linkgit:git-clone[1]. By + default, only commit and tree objects are cloned. Once finished, the + worktree is located at `<enlistment>/src`. ++ +The sparse-checkout feature is enabled (except when run with `--full-clone`) +and the only files present are those in the top-level directory. Use +`git sparse-checkout set` to expand the set of directories you want to see, +or `git sparse-checkout disable` to expand to all files (see +linkgit:git-sparse-checkout[1] for more details). You can explore the +subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. + +-b <name>:: +--branch <name>:: + Instead of checking out the branch pointed to by the cloned + repository's HEAD, check out the `<name>` branch instead. + +--[no-]full-clone:: + A sparse-checkout is initialized by default. This behavior can be + turned off via `--full-clone`. + List ~~~~ list:: To see which repositories are currently registered by the service, run - `scalar list`. This subcommand does not need to be run inside a Scalar - enlistment. + `scalar list`. This subcommand, like `clone`, does not need to be run + inside a Scalar enlistment. Register ~~~~~~~~
@@ -65,7 +90,7 @@ unregister [<enlistment>]:: SEE ALSO -------- -linkgit:git-maintenance[1]. +linkgit:git-clone[1], linkgit:git-maintenance[1]. Scalar ---
contrib/scalar/t/t9099-scalar.sh +32 -0
@@ -10,6 +10,9 @@ PATH=$PWD/..:$PATH . ../../../t/test-lib.sh +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt" +export GIT_TEST_MAINT_SCHEDULER + test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h '
@@ -29,4 +32,33 @@ test_expect_success 'scalar unregister' ' ! grep -F "$(pwd)/vanish/src" scalar.repos ' +test_expect_success 'set up repository to clone' ' + test_commit first && + test_commit second && + test_commit third && + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + git config uploadPack.allowFilter true && + git config uploadPack.allowAnySHA1InWant true +' + +test_expect_success 'scalar clone' ' + second=$(git rev-parse --verify second:second.t) && + scalar clone "file://$(pwd)" cloned && + ( + cd cloned/src && + + git config --get --global --fixed-value maintenance.repo \ + "$(pwd)" && + + test_path_is_missing 1/2 && + test_must_fail git rev-list --missing=print $second && + git rev-list $second && + git cat-file blob $second >actual && + echo "second" >expect && + test_cmp expect actual + ) +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 9/15 scalar: teach 'clone' to support the --single-branch option
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like git clone, the scalar clone command now also offers to restrict the clone to a single branch.
--- contrib/scalar/scalar.c | 9 +++++++-- contrib/scalar/scalar.txt | 12 +++++++++++- contrib/scalar/t/t9099-scalar.sh | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-)
contrib/scalar/scalar.c +7 -2
@@ -333,12 +333,15 @@ static char *remote_default_branch(const char *url)static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ; -int full_clone =0 ; +int full_clone =0 , single_branch =0 ;struct option clone_options[] = {OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ),N_ ("branch to checkout after clone" )),OPT_BOOL (0 ,"full-clone" , &full_clone,N_ ("when cloning, create full working directory" )), +OPT_BOOL (0 ,"single-branch" , &single_branch, +N_ ("only download metadata for the branch that will " +"be checked out" )),OPT_END (), };const char *const clone_usage[] = {
@@ -409,7 +412,9 @@ static int cmd_clone(int argc, const char **argv)if (set_config ("remote.origin.url=%s" , url) ||set_config ("remote.origin.fetch=" -"+refs/heads/*:refs/remotes/origin/*" ) || +"+refs/heads/%s:refs/remotes/origin/%s" , + single_branch ? branch :"*" , + single_branch ? branch :"*" ) ||set_config ("remote.origin.promisor=true" ) ||set_config ("remote.origin.partialCloneFilter=blob:none" )) { res =error (_ ("could not configure remote in '%s'" ), dir);
contrib/scalar/scalar.txt +11 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] +scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -56,6 +56,16 @@ subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. Instead of checking out the branch pointed to by the cloned repository's HEAD, check out the `<name>` branch instead. +--[no-]single-branch:: + Clone only the history leading to the tip of a single branch, either + specified by the `--branch` option or the primary branch remote's + `HEAD` points at. ++ +Further fetches into the resulting repository will only update the +remote-tracking branch for the branch this option was used for the initial +cloning. If the HEAD at the remote did not point at any branch when +`--single-branch` clone was made, no remote-tracking branch is created. + --[no-]full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`.
contrib/scalar/t/t9099-scalar.sh +5 -1
@@ -45,13 +45,17 @@ test_expect_success 'set up repository to clone' ' test_expect_success 'scalar clone' ' second=$(git rev-parse --verify second:second.t) && - scalar clone "file://$(pwd)" cloned && + scalar clone "file://$(pwd)" cloned --single-branch && ( cd cloned/src && git config --get --global --fixed-value maintenance.repo \ "$(pwd)" && + git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual && + echo "refs/remotes/origin/parallel" >expect && + test_cmp expect actual && + test_path_is_missing 1/2 && test_must_fail git rev-list --missing=print $second && git rev-list $second &&
Signature · 1 line
gitgitgadget
PATCH v2 10/15 scalar: implement the `run` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Note: this subcommand is provided primarily for backwards-compatibility, for existing Scalar uses. It is mostly just a shim for git maintenance, mapping task names from the way Scalar called them to the way Git calls them.
The reason why those names differ? The background maintenance was first implemented in Scalar, and when it was contributed as a patch series implementing the git maintenance command, reviewers suggested better names, those suggestions were accepted before the patches were integrated into core Git.
--- contrib/scalar/scalar.c | 64 +++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 19 ++++++++++++ 2 files changed, 83 insertions(+)
contrib/scalar/scalar.c +64 -0
@@ -490,6 +490,69 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_run (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +struct { +const char *arg, *task; + } tasks[] = { + {"config" ,NULL }, + {"commit-graph" ,"commit-graph" }, + {"fetch" ,"prefetch" }, + {"loose-objects" ,"loose-objects" }, + {"pack-files" ,"incremental-repack" }, + {NULL ,NULL } + }; +struct strbuf buf =STRBUF_INIT ; +const char *usagestr[] = {NULL ,NULL }; +int i; + +strbuf_addstr (&buf,N_ ("scalar run <task> [<enlistment>]\nTasks:\n" )); +for (i =0 ; tasks[i].arg; i++) +strbuf_addf (&buf,"\t%s\n" , tasks[i].arg); + usagestr[0 ] = buf.buf; + + argc =parse_options (argc, argv,NULL , options, + usagestr,0 ); + +if (argc ==0 ) +usage_with_options (usagestr, options); + +if (!strcmp ("all" , argv[0 ])) + i =-1 ; +else { +for (i =0 ; tasks[i].arg &&strcmp (tasks[i].arg, argv[0 ]); i++) + ;/* keep looking for the task */ + +if (i >0 && !tasks[i].arg) { +error (_ ("no such task: '%s'" ), argv[0 ]); +usage_with_options (usagestr, options); + } + } + + argc--; + argv++; +setup_enlistment_directory (argc, argv, usagestr, options,NULL ); +strbuf_release (&buf); + +if (i ==0 ) +return register_dir (); + +if (i >0 ) +return run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL ); + +if (register_dir ()) +return -1 ; +for (i =1 ; tasks[i].arg; i++) +if (run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL )) +return -1 ; +return 0 ; +} +static int remove_deleted_enlistment (struct strbuf *path) {int res =0 ;
@@ -562,6 +625,7 @@ static struct { { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, + { "run", cmd_run }, { NULL, NULL}, };
contrib/scalar/scalar.txt +19 -0
@@ -12,6 +12,7 @@ scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<e scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] DESCRIPTION -----------
@@ -98,6 +99,24 @@ unregister [<enlistment>]:: Remove the specified repository from the list of repositories registered with Scalar and stop the scheduled background maintenance. +Run +~~~ + +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]:: + Run the given maintenance task (or all tasks, if `all` was specified). + Except for `all` and `config`, this subcommand simply hands off to + linkgit:git-maintenance[1] (mapping `fetch` to `prefetch` and + `pack-files` to `incremental-repack`). ++ +These tasks are run automatically as part of the scheduled maintenance, +as soon as the repository is registered with Scalar. It should therefore +not be necessary to run this subcommand manually. ++ +The `config` task is specific to Scalar and configures all those +opinionated default settings that make Git work more efficiently with +large repositories. As this task is run as part of `scalar clone` +automatically, explicit invocations of this task are rarely needed. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v2 12/15 scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
After a Scalar upgrade, it can come in really handy if there is an easy way to reconfigure all Scalar enlistments. This new option offers this functionality.
--- contrib/scalar/scalar.c | 61 ++++++++++++++++++++++++++++++-- contrib/scalar/scalar.txt | 10 ++++-- contrib/scalar/t/t9099-scalar.sh | 3 ++ 3 files changed, 68 insertions(+), 6 deletions(-)
contrib/scalar/scalar.c +58 -3
@@ -494,22 +494,77 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int get_scalar_repos (const char *key,const char *value,void *data) +{ +struct string_list *list = data; + +if (!strcmp (key,"scalar.repo" )) +string_list_append (list, value); + +return 0 ; +} +static int cmd_reconfigure (int argc,const char **argv) { +int all =0 ;struct option options[] = { +OPT_BOOL ('a' ,"all" , &all, +N_ ("reconfigure all registered enlistments" )),OPT_END (), };const char *const usage[] = { -N_ ("scalar reconfigure [<enlistment>]" ), +N_ ("scalar reconfigure [--all | <enlistment>]" ),NULL }; +struct string_list scalar_repos =STRING_LIST_INIT_DUP ; +int i, res =0 ; +struct repository r = {NULL }; +struct strbuf commondir =STRBUF_INIT , gitdir =STRBUF_INIT ; argc =parse_options (argc, argv,NULL , options, usage,0 ); -setup_enlistment_directory (argc, argv, usage, options,NULL ); +if (!all) { +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); + } + +if (argc >0 ) +usage_msg_opt (_ ("--all or <enlistment>, but not both" ), + usage, options); + +git_config (get_scalar_repos, &scalar_repos); -return set_recommended_config (1 ); +for (i =0 ; i < scalar_repos.nr; i++) { +const char *dir = scalar_repos.items[i].string; + +strbuf_reset (&commondir); +strbuf_reset (&gitdir); + +if (chdir (dir) <0 ) { +warning_errno (_ ("could not switch to '%s'" ), dir); + res =-1 ; + }else if (discover_git_directory (&commondir, &gitdir) <0 ) { +warning_errno (_ ("git repository gone in '%s'" ), dir); + res =-1 ; + }else { +git_config_clear (); + + the_repository = &r; + r.commondir = commondir.buf; + r.gitdir = gitdir.buf; + +if (set_recommended_config (1 ) <0 ) + res =-1 ; + } + } + +string_list_clear (&scalar_repos,1 ); +strbuf_release (&commondir); +strbuf_release (&gitdir); + +return res; }static int cmd_run (int argc,const char **argv)
contrib/scalar/scalar.txt +7 -3
@@ -13,7 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] -scalar reconfigure <enlistment> +scalar reconfigure [ --all | <enlistment> ] DESCRIPTION -----------
@@ -32,8 +32,8 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `clone` and `list`, all -subcommands expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone`, `list` and +`reconfigure --all`, all subcommands expect to be run in an enlistment. COMMANDS --------
@@ -125,6 +125,10 @@ After a Scalar upgrade, or when the configuration of a Scalar enlistment was somehow corrupted or changed by mistake, this subcommand allows to reconfigure the enlistment. +With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time after +Scalar is upgraded. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +3 -0
@@ -70,6 +70,9 @@ test_expect_success 'scalar reconfigure' ' scalar register one && git -C one/src config core.preloadIndex false && scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" && + git -C one/src config core.preloadIndex false && + scalar reconfigure -a && test true = "$(git -C one/src config core.preloadIndex)" '
Signature · 1 line
gitgitgadget
PATCH v2 13/15 scalar: implement the `delete` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Matthew John Cheetham <[email protected]>
Delete an enlistment by first unregistering the repository and then deleting the enlistment directory (usually the directory containing the worktree src/ directory).
On Windows, if the current directory is inside the enlistment's directory, change to the parent of the enlistment directory, to allow us to delete the enlistment (directories used by processes e.g. as current working directories cannot be deleted on Windows).
--- contrib/scalar/scalar.c | 55 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 +++++ contrib/scalar/t/t9099-scalar.sh | 9 ++++++ 3 files changed, 72 insertions(+)
contrib/scalar/scalar.c +55 -0
@@ -8,6 +8,7 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -334,6 +335,33 @@ static char *remote_default_branch(const char *url)return NULL ; } +static int delete_enlistment (struct strbuf *enlistment) +{ +#ifdef WIN32 +struct strbuf parent =STRBUF_INIT ; +#endif + +if (unregister_dir ()) +die (_ ("failed to unregister repository" )); + +#ifdef WIN32 +/* +* Change the current directory to one outside of the enlistment so +* that we may delete everything underneath it. +*/ +strbuf_addbuf (&parent, enlistment); +strbuf_parent_directory (&parent); +if (chdir (parent.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), parent.buf); +strbuf_release (&parent); +#endif + +if (remove_dir_recursively (enlistment,0 )) +die (_ ("failed to delete enlistment directory" )); + +return 0 ; +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -694,6 +722,32 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar delete <enlistment>" ), +NULL + }; +struct strbuf enlistment =STRBUF_INIT ; +int res =0 ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=1 ) +usage_with_options (usage, options); + +setup_enlistment_directory (argc, argv, usage, options, &enlistment); + + res =delete_enlistment (&enlistment); +strbuf_release (&enlistment); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -704,6 +758,7 @@ static struct { { "unregister", cmd_unregister }, { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, + { "delete", cmd_delete }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -14,6 +14,7 @@ scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] scalar reconfigure [ --all | <enlistment> ] +scalar delete <enlistment> DESCRIPTION -----------
@@ -129,6 +130,13 @@ With the `--all` option, all enlistments currently registered with Scalar will be reconfigured. This option is meant to to be run every time after Scalar is upgraded. +Delete +~~~~~~ + +delete <enlistment>:: + This subcommand lets you delete an existing Scalar enlistment from your + local file system, unregistering the repository. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +9 -0
@@ -76,4 +76,13 @@ test_expect_success 'scalar reconfigure' ' test true = "$(git -C one/src config core.preloadIndex)" ' +test_expect_success 'scalar delete without enlistment shows a usage' ' + test_expect_code 129 scalar delete +' + +test_expect_success 'scalar delete with enlistment' ' + scalar delete cloned && + test_path_is_missing cloned +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 11/15 scalar: allow reconfiguring an existing enlistment
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
--- contrib/scalar/scalar.c | 79 +++++++++++++++++++++----------- contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 8 ++++ 3 files changed, 67 insertions(+), 28 deletions(-)
contrib/scalar/scalar.c +51 -28
@@ -115,18 +115,20 @@ static int run_git(const char *arg, ...)return res; } -static int set_recommended_config (void ) +static int set_recommended_config (int reconfigure) {struct {const char *key;const char *value; +int overwrite_on_reconfigure; } config[] = { - {"am.keepCR" ,"true" }, - {"core.FSCache" ,"true" }, - {"core.multiPackIndex" ,"true" }, - {"core.preloadIndex" ,"true" }, +/* Required */ + {"am.keepCR" ,"true" ,1 }, + {"core.FSCache" ,"true" ,1 }, + {"core.multiPackIndex" ,"true" ,1 }, + {"core.preloadIndex" ,"true" ,1 }, #ifndef WIN32 - {"core.untrackedCache" ,"true" }, + {"core.untrackedCache" ,"true" ,1 }, #else /* * Unfortunately, Scalar's Functional Tests demonstrated
@@ -140,28 +142,29 @@ static int set_recommended_config(void) * Therefore, with a sad heart, we disable this very useful * feature on Windows. */ - { "core.untrackedCache", "false" }, + { "core.untrackedCache", "false", 1 }, #endif - { "core.logAllRefUpdates", "true" }, - { "credential.https://dev.azure.com.useHttpPath", "true" }, - { "credential.validate", "false" }, /* GCM4W-only */ - { "gc.auto", "0" }, - { "gui.GCWarning", "false" }, - { "index.threads", "true" }, - { "index.version", "4" }, - { "merge.stat", "false" }, - { "merge.renames", "false" }, - { "pack.useBitmaps", "false" }, - { "pack.useSparse", "true" }, - { "receive.autoGC", "false" }, - { "reset.quiet", "true" }, - { "feature.manyFiles", "false" }, - { "feature.experimental", "false" }, - { "fetch.unpackLimit", "1" }, - { "fetch.writeCommitGraph", "false" }, + { "core.logAllRefUpdates", "true", 1 }, + { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, + { "credential.validate", "false", 1 }, /* GCM4W-only */ + { "gc.auto", "0", 1 }, + { "gui.GCWarning", "false", 1 }, + { "index.threads", "true", 1 }, + { "index.version", "4", 1 }, + { "merge.stat", "false", 1 }, + { "merge.renames", "false", 1 }, + { "pack.useBitmaps", "false", 1 }, + { "pack.useSparse", "true", 1 }, + { "receive.autoGC", "false", 1 }, + { "reset.quiet", "true", 1 }, + { "feature.manyFiles", "false", 1 }, + { "feature.experimental", "false", 1 }, + { "fetch.unpackLimit", "1", 1 }, + { "fetch.writeCommitGraph", "false", 1 }, #ifdef WIN32 - { "http.sslBackend", "schannel" }, + { "http.sslBackend", "schannel", 1 }, #endif + /* Optional */ { "status.aheadBehind", "false" }, { "commitGraph.generationVersion", "1" }, { "core.autoCRLF", "false" },
@@ -172,7 +175,8 @@ static int set_recommended_config(void)char *value;for (i =0 ; config[i].key; i++) { -if (git_config_get_string (config[i].key, &value)) { +if ((reconfigure && config[i].overwrite_on_reconfigure) || +git_config_get_string (config[i].key, &value)) {trace2_data_string ("scalar" , the_repository, config[i].key,"created" );if (git_config_set_gently (config[i].key, config[i].value) <0 )
@@ -237,7 +241,7 @@ static int register_dir(void)int res =add_or_remove_enlistment (1 );if (!res) - res =set_recommended_config (); + res =set_recommended_config (0 );if (!res) res =toggle_maintenance (1 );
@@ -425,7 +429,7 @@ static int cmd_clone(int argc, const char **argv) (res = run_git("sparse-checkout", "init", "--cone", NULL))) goto cleanup; -if (set_recommended_config ()) +if (set_recommended_config (0 ))return error (_ ("could not configure '%s'" ), dir);if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) {
@@ -490,6 +494,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_reconfigure (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar reconfigure [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); +} +static int cmd_run (int argc,const char **argv) {struct option options[] = {
@@ -626,6 +648,7 @@ static struct { { "register", cmd_register }, { "unregister", cmd_unregister }, { "run", cmd_run }, + { "reconfigure", cmd_reconfigure }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -13,6 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] +scalar reconfigure <enlistment> DESCRIPTION -----------
@@ -117,6 +118,13 @@ opinionated default settings that make Git work more efficiently with large repositories. As this task is run as part of `scalar clone` automatically, explicit invocations of this task are rarely needed. +Reconfigure +~~~~~~~~~~~ + +After a Scalar upgrade, or when the configuration of a Scalar enlistment +was somehow corrupted or changed by mistake, this subcommand allows to +reconfigure the enlistment. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +8 -0
@@ -65,4 +65,12 @@ test_expect_success 'scalar clone' ' ) ' +test_expect_success 'scalar reconfigure' ' + git init one/src && + scalar register one && + git -C one/src config core.preloadIndex false && + scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 14/15 scalar: implement the `version` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The .NET version of Scalar has a version command. This was necessary because it was versioned independently of Git.
Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as git version. For backwards-compatibility with the .NET version, scalar version prints to stderr, though (git version prints to stdout instead).
--- contrib/scalar/scalar.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
contrib/scalar/scalar.c +39 -0
@@ -9,6 +9,7 @@#include "run-command.h" #include "refs.h" #include "dir.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -362,6 +363,15 @@ static int delete_enlistment(struct strbuf *enlistment)return 0 ; } +/* +* Dummy implementation; Using `get_version_info()` would cause a link error +* without this. +*/ +void load_builtin_commands (const char *prefix,struct cmdnames *cmds) +{ +die ("not implemented" ); +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -748,6 +758,34 @@ static int cmd_delete(int argc, const char **argv)return res; } +static int cmd_version (int argc,const char **argv) +{ +int verbose =0 , build_options =0 ; +struct option options[] = { +OPT__VERBOSE (&verbose,N_ ("include Git version" )), +OPT_BOOL (0 ,"build-options" , &build_options, +N_ ("include Git's build options" )), +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar verbose [-v | --verbose] [--build-options]" ), +NULL + }; +struct strbuf buf =STRBUF_INIT ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=0 ) +usage_with_options (usage, options); + +get_version_info (&buf, build_options); +fprintf (stderr,"%s\n" , buf.buf); +strbuf_release (&buf); + +return 0 ; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -759,6 +797,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
PATCH v2 15/15 scalar: accept -C and -c options before the subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The git executable has these two very useful options:
-C <directory>:
switch to the specified directory before performing any actions
-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommand
With this commit, we teach the scalar executable the same trick.
--- contrib/scalar/scalar.c | 22 +++++++++++++++++++++- contrib/scalar/scalar.txt | 10 ++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +21 -1
@@ -806,6 +806,25 @@ int cmd_main(int argc, const char **argv)struct strbuf scalar_usage =STRBUF_INIT ;int i; +while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ; + argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + } +if (argc >1 ) { argv++; argc--;
@@ -816,7 +835,8 @@ int cmd_main(int argc, const char **argv) }strbuf_addstr (&scalar_usage, -N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +N_ ("scalar [-C <directory>] [-c <key>=<value>] " +"<command> [<options>]\n\nCommands:\n" ));for (i =0 ; builtins[i].name; i++)strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name);
contrib/scalar/scalar.txt +10 -0
@@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options depending on the subcommand. With the exception of `clone`, `list` and `reconfigure --all`, all subcommands expect to be run in an enlistment. +The following options can be specified _before_ the subcommand: + +-C <directory>:: + Before running the subcommand, change the working directory. This + option imitates the same option of linkgit:git[1]. + +-c <key>=<value>:: + For the duration of running the specified subcommand, configure this + setting. This option imitates the same option of linkgit:git[1]. + COMMANDS --------
Signature · 1 line
gitgitgadget
Re: [PATCH v2 07/15] scalar: implement 'scalar list'
In reply to Derrick Stolee via GitGitGadgetcontrib/scalar/scalar.txt an existing Git worktree with Scalar whose name is not `src`, the enlistment · line 45
Quoted 8 lines · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.txt +8
-0+List +~~~~ + +list:: + To see which repositories are currently registered by the service, run + `scalar list`. This subcommand does not need to be run inside a Scalar + enlistment. +
I think the man-page-style wording should be:
Quoted 3 lines
list::
List enlistments that are currently registered by Scalar. This subcommand does not need to be run inside an enlistment.
Signature · 1 line
An old man doll... just what I always wanted! - Clara
Re: [PATCH 11/15] scalar: allow reconfiguring an existing enlistment
In reply to Johannes SchindelinQuoted 17 lines · Johannes Schindelin
Hi Ævar,
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Johannes Schindelin via GitGitGadget
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
Quoted 6 lines
contrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 123
Quoted 4 lines
[...]
contrib/scalar/scalar.c +1
-0const char *key;const char *value; +int overwrite_on_reconfigure;If you make this a "
keep_on_reconfigure", then ...
Quoted 2 lines
I do not think that this would be a better name, or that renaming this field would do anything except cause more work for me.
It would also result in more readable code, i.e. why add boilerplate ", 1" to a boolean field in this case if every single setting is set to "1"? Doesn't it make more sense to invert the variable name & save on the verbosity?
Quoted 17 lines
Quoted 17 lines
contrib/scalar/scalar.c static int run_git(const char *arg, ...) · line 129
Quoted 11 lines
contrib/scalar/scalar.c +5
-4} config[] = { - { "am.keepCR", "true" }, - { "core.FSCache", "true" }, - { "core.multiPackIndex", "true" }, - { "core.preloadIndex", "true" }, +/* Required */ + { "am.keepCR", "true", 1 }, + { "core.FSCache", "true", 1 }, + { "core.multiPackIndex", "true", 1 }, + { "core.preloadIndex", "true", 1 },You won't need the churn/boilerplate of adding "1" to everything here, but can just change the initial patch to use designated initializers.
That along with a throwaway macro like:
Quoted 8 lines
Quoted 4 lines
#define
SCALAR_CFG_TRUE(k)(.key = k, .value = "true") #defineSCALAR_CFG_FALSE(k)(.key = k, .value = "false")Might (or might not) make this even easier to eyeball...
To me, it makes things less readable. There is an entire section with the header
/* Optional */below, and I want this list to stay as readable as it is now.
Yeah, I think those macros are probably less readable too. I should have phrased that as a "one could even...", but just the smaller change of avoiding the ", 1" everywhere seems worthwhile.
Quoted 6 lines · Johannes Schindelin via GitGitGadget
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with Scalar
A summary of outstanding but unaddressed things would be useful during re-rolls. In this case at least:
- My point that the build system part of this is more complex than it probably needs to be:
https://lore.kernel.org/git/[email protected]/
- My point & Junio concurring with some style suggestions:
https://lore.kernel.org/git/[email protected]
- My minor style comment about the ", 1". Just saying it's "outstanding" because I think what you had & I just replied to in
https://lore.kernel.org/git/[email protected]/ might have been a dismissal of the macro suggestion (which I don't like either), not just the simpler "get rid of init verbosity to all 1".
Re: [PATCH v2 08/15] scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetQuoted 8 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated
clonecommand: it tries to use a partial clone and sets up a sparse checkout by default. In contrast togit clone,scalar clonesets up the worktree in thesrc/subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Perhaps it's simpler to just say about that /src/ injection:
scalar clone adds an implicit "/src" subdirectory to whatever directory the user provides, with the added stricture on top of doing that with "git clone" that the "src" cannot exist already.
...
contrib/scalar/scalar.c static int unregister_dir(void) · line 381
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment);
Which also seems to suggest a bug here. I.e. if I "git clone <repo> /tmp/xyz/abc" and Ctrl+C it we'll remove "abc", but leave "xyz" behind. Since we're creating that "xyz" (or "src") implicitly here an abort/ctrl+C followed by a retry is going to run into this error, isn't it?
I.e. it seems what's missing in this state machine is checking if the directory was there already, and if it isn't add it to the existing atexit() removals.
Which may be tricky seeing as this is shelling out to "init" then "fetch" etc, i.e. who removes it? But maybe not.
Re: [PATCH 12/15] scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Eric SunshineHi Eric,
Quoted 19 lines · Eric Sunshine
Quoted 10 lines · Johannes Schindelin
Quoted 6 lines · Eric Sunshine
On Mon, Aug 30, 2021 at 5:35 PM Johannes Schindelin via GitGitGadget
contrib/scalar/scalar.txt After a Scalar upgrade, or when the configuration of a Scalar enlistment · line 126
Quoted 3 lines
contrib/scalar/scalar.txt +3
-0+With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. This option is meant to to be run every time Scalar +was upgraded.s/was/is/
I wanted to convey a temporal order, so I changed it to "every time after Scalar is upgraded". Okay?
I think I understood the intent of the original, but it causes a grammatical hiccup. Your revised version can work, although I might write it this way:
This option is meant to be run each time Scalar is upgraded.
Quoted 8 lines
However, perhaps that is too ambiguous and some users may think that the process of upgrading Scalar will automatically run this command, and you'd like to make it clear that it is the user's responsibility. So, perhaps:
Use this option after each Scalar upgrade.
or something.
I like the last one best, too.
Thank you,
Dscho
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Junio C HamanoHi Junio,
Quoted 34 lines · Junio C Hamano
Quoted 32 lines · Johannes Schindelin
It would even work if the current line is shorter, but as you point out:
it is wasteful. And it could be improved to be more readable. I reworked it, and it now looks like this:if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { const char *line = out.buf; while (*line) { const char *eol = strchrnul(line, '\n'), *p; size_t len = eol - line; char *branch; if (!skip_prefix(line, "ref: ", &p) || !strip_suffix_mem(line, &len, "\tHEAD")) { line = eol + (*eol == '\n'); continue; } eol = line + len; if (skip_prefix(p, "refs/heads/", &p)) { branch = xstrndup(p, eol - p); strbuf_release(&out); return branch; } error(_("remote HEAD is not a branch: '%.*s'"), (int)(eol - p), p); strbuf_release(&out); return NULL; } }
Quoted 6 lines
Quoted 3 lines
It now parses the output line by line, looking for the expected prefix and suffix, then verifies the ref name format, and either returns the short branch name or errors out with the message that this is not a branch.
It is much easier to read and understand how the loop works with above.
Excellent.
Quoted 18 lines
Quoted 12 lines
Quoted 11 lines
contrib/scalar/scalar.c static int unregister_dir(void) · line 313
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(head - ref), ref); +strbuf_release (&out); +return NULL ;OK. Any symref whose basename is HEAD in their remote-tracking hierarchy would have been rejected earlier in the loop.
Is there a particular reason why we return early here, instead of breaking out of hte loop and let the generic "failed to get" code path below to handle this case?
Quoted 5 lines
Yes, the reason is that I wanted to err on the side of caution. If the remote repository reports a default branch that is not a default branch at all, I do not want to pretend that things are fine and then run into trouble later when we set up a non-branch as remote-tracking target or something like that.
Quoted 5 lines
Wouldn't we have the same problem when the remote end does not advertise HEAD and we fall back to "local default", though? We'd run into trouble later as we use "local default" that may correspond to a non-branch there as remote-tracking target, or something like that.
All true, there will be trouble at some stage.
The difference between the two cases, in my mind, is that cloning an empty repository would run into the latter code path and might still have a chance to work as intended by using the local default branch name.
In any case, as I indicated earlier, I am _very_ interested in moving as much functionality as possible from Scalar to Git proper. In this instance, I hope to move most of the code from scalar.c to clone.c (guarded by one or more new options). And as soon as that happens, the discussion we're having right now will be moot ;-)
Which means that I want to weigh how much effort to put into polishing an unlikely code path on one side, and on the other side how much effort to put into moving the functionality away from contrib/ and deleting that unlikely code path.
In the same vein, while this patch series contains (mostly) code in contrib/ (and therefore technically does not need to adhere strictly to Git's code style), it is probably wise to pay closer attention to the code style particularly in those parts that are prone to be moved verbatim (or close to verbatim) to Git proper.
Thanks,
Dscho
Quoted 7 lines
Not that I care too deeply in the error case, though. I just felt that this early return was an uneven way to follow the principle to err on the side of caution, as we continue with the local default when the other side fails to tell us what their HEAD points at.
Thanks.
Re: [PATCH 10/15] scalar: implement the `run` command
In reply to Junio C HamanoHi Junio,
Quoted 36 lines · Junio C Hamano
Quoted 14 lines · Johannes Schindelin
Hi Ævar,
Quoted 5 lines · Ævar Arnfjörð Bjarmason
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 509
Quoted 1 line · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.c +1
-0+const char *usagestr[] = {NULL ,NULL };Missing usage strings?
This command will show a generated usage, i.e. a non-static string. It therefore cannot be specified here already. See the
strbuf_*()calls populatingbufand theusagestr[0] = buf.buf;assignment.Quoted 6 lines
Quoted 3 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 520
Quoted 1 line
contrib/scalar/scalar.c +1
-0+if (argc ==0 )Style nit (per style guide): s/argc == 0/!argc/g.
It is true that we often do this, but in this instance it would be
misleading:argcis a counter, not a Boolean.That argument could be a plausible excuse to deviate from the style if it were
if (argc == 0) do no args case; else if (argc == 1) do one arg case; else if (argc == 2) do two args case; ...Replacing the first one with "if (!argc)" may make it less readable.
Quoted 7 lines
But I do not think the reasoning applies here
if (argc == 0) do a thing that applies only to no args case;without "else". This is talking about "do we have any argument? Yes or no?" Boolean here.
Well, I offer a differing opinion. But you're right, we are at least consistent in Git's source code in using !i where other projects would use i == 0, and consistency is definitely something I'd like to see more in Git, not less.
So I changed it as you suggested.
Quoted 9 lines
Quoted 8 lines
Quoted 5 lines
contrib/scalar/scalar.c static int cmd_register(int argc, const char **argv) · line 524
Quoted 3 lines
contrib/scalar/scalar.c +2
-0+if (!strcmp ("all" , argv[0 ])) + i =-1 ;Style nit (per style guide): missing braces here.
The style guide specifically allows my preference to leave single-line blocks without curlies.
Quoted 7 lines
Actually, the exception goes the other way, no?
We generally want to avoid such an unnecessary braces around a single statement block. But when we have an else clause that has a block with multiple statements (hence braces are required), as an exception, the guide asks you to write braces around the body of the if side for consistency.
You're right. I am somehow still using the previous style where we _required_ single-line blocks _not_ to have curly brackets (see e.g. aa1c48df817 ([PATCH] ls-tree enhancements, 2005-04-15), the else part of the added if (! eltbuf) block).
Quoted 5 lines
When you only have just a couple of lines on the "else {}" side, I do not think it matters too much either way for readability, though. I cannot see the "else" side in the above clause, but IIRC it wasn't just a few lines, was it?
It depends what you count as "just a few lines". There are seven lines enclosed within the curly brackets of the else block.
But as much as I enjoy thorough reviews of the Scalar code, I am failing at getting excited about code style discussions, therefore I simply went with your suggestion to enclose even the single-line block in curly brackets.
Thanks,
Dscho
Re: [PATCH v2 07/15] scalar: implement 'scalar list'
In reply to Bagas SanjayaHi Bagas,
Quoted 15 lines · Bagas Sanjaya
contrib/scalar/scalar.txt an existing Git worktree with Scalar whose name is not `src`, the enlistment · line 45
Quoted 8 lines · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.txt +8
-0+List +~~~~ + +list:: + To see which repositories are currently registered by the service, run + `scalar list`. This subcommand does not need to be run inside a Scalar + enlistment. +I think the man-page-style wording should be:
Quoted 3 lines
list::
List enlistments that are currently registered by Scalar. This subcommand does not need to be run inside an enlistment.
Thank you, I adopted that wording.
Ciao,
Dscho
Re: [PATCH v2 08/15] scalar: implement the `clone` subcommand
In reply to Ævar Arnfjörð BjarmasonHi Ævar,
Quoted 12 lines · Ævar Arnfjörð Bjarmason
Quoted 8 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated
clonecommand: it tries to use a partial clone and sets up a sparse checkout by default. In contrast togit clone,scalar clonesets up the worktree in thesrc/subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Quoted 5 lines
Perhaps it's simpler to just say about that /src/ injection:
scalar cloneadds an implicit "/src" subdirectory to whatever directory the user provides, with the added stricture on top of doing that with "git clone" that the "src" cannot exist already.
It would not only be simpler, it would also skip an important point I tried to make, namely how this _differs_ from git clone. Rather crucial, really.
Quoted 12 lines
...
contrib/scalar/scalar.c static int unregister_dir(void) · line 381
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment);Which also seems to suggest a bug here. I.e. if I "git clone <repo>
/tmp/xyz/abc" and Ctrl+C it we'll remove "abc", but leave "xyz" behind. Since we're creating that "xyz" (or "src") implicitly here an abort/ctrl+C followed by a retry is going to run into this error, isn't it?
Sure, it's just like calling git clone <url> a/b/c and upon failure seeing only the c directory removed, while a/b is left behind.
Quoted 6 lines
I.e. it seems what's missing in this state machine is checking if the directory was there already, and if it isn't add it to the existing
atexit()removals.Which may be tricky seeing as this is shelling out to "init" then "fetch" etc, i.e. who removes it? But maybe not.
I would rather spend time (after this patch series landed, of course) on teaching git clone to handle what Scalar needs, and upon Ctrl+C to optionally remove _all_ the directories it created, not just the innermost one.
Then we get that functionality "for free", without spending a lot of time on code that will be obsolete soon enough anyway.
Ciao,
Johannes
v3 cover Upstreaming the Scalar command
In reply to Johannes Schindelin via GitGitGadgettl;dr: This series contributes the Scalar command to the Git project. This command provides an opinionated way to create and configure repositories with a focus on very large repositories.
Background
==========
Years ago, Microsoft wanted to move the source code of the Windows operating system to Git. The challenge there was to prove that Git could scale to massive monorepos. The VFS for Git (formerly GVFS) project was born to take up that challenge.
The final solution included a virtual filesystem (with both user-mode and kernel components) and a customized fork of Git for Windows. This solution contained several key concepts, such as only populating a portion of the working directory, demand-fetching blobs, and performing periodic repo maintenance in the background. However, the required kernel drivers made it difficult to port the solution to other platforms.
But it was realized that many of these key concepts were independent of the actual VFS and its projection of the working directory. The Scalar project was created to make that separation, refine the key concepts, and then extract those features into the new Scalar command.
The present
===========
The Scalar project provides a completely functional non-virtual experience for monorepos. But why stop there. The Scalar project was designed to be a self-destructing vehicle to allow those key concepts to be moved into core Git itself for the benefit of all. For example, partial clone, sparse-checkout, and background maintenance have already been upstreamed and removed from Scalar proper. This patch series provides a C-based implementation of the final remaining portions of the Scalar command. This will make it easier for users to experiment with the Scalar command. It will also make it substantially easier to experiment with moving functionality from Scalar into core Git, while maintaining backwards-compatibility for existing Scalar users.
The C-based Scalar has been shipped to Scalar users, and can be tested by any interested reader:
https://github.com/microsoft/git/releases/tag/v2.33.0.vfs.0.0 (it offers a Git for Windows installer, a macOS package and an Ubuntu package).
Opportunities
=============
Apart from providing the Scalar command, this contribution is intended to serve as a basis for further mailing list discussions on moving (some of) these key concepts into the main Git commands.
For example, we previously discussed the idea of a "git big-clone" that does much of what "scalar clone" is doing. This patch series is a step to make such functionality exist in the Git code base while we simmer on what such a "git big-clone" command-line interface would look like.
This is one of many possible ways to do this. Creating a 'git big-clone' could lock Git into backwards compatibility concerns so it is necessary to approach such an endeavor with caution. As a discussion starter, the scalar clone <url> command does roughly this:
1. git clone --sparse --filter=blob:none /src
2. git -C /src sparse-checkout init --cone
3. git -C /src config (many times)
4. git -C /src maintenance start
It is my hope inspire discussions about what parts of Scalar could go into core Git, and where, and in which form. While we wish to maintain backwards-compatibility of Scalar's command-line interface (because it is already in use), by having the Scalar code in the same code base as Git's, it will be much easier to move functionality without having to maintain loose version coupling between independently-versioned Scalar and Git. The tight version-coupling, along with having access to libgit.a also allows the C-based implementation of Scalar to be much smaller than the original .NET version.
For example, we might choose in the future to implement, say, git clone --scale=partial,cone to initialize a partial clone with a cone-sparse checkout, that would not only be totally doable, and not only would we already have precedent and data to prove that this actually makes engineers happy who have to work on ginormous repositories, but we could then also implement it by moving parts of contrib/scalar/ to builtin/ (where contrib/scalar/ would then call the built-ins accordingly rather than hard-coding the defaults itself).
We now also have the opportunity to discuss the merits of Scalar's clone caching, which is not actually part of this patch series because it is a bit coupled with the GVFS parts of microsoft/git for the moment, where clones automatically get registered with a populated alternate repository that is identified by the URL, meaning: subsequent clones of the same repository are vastly faster than the first one because they do not actually download the already-received objects again, they access the cache instead.
Another thing that I could imagine to be discussed at length is the distinction between enlistment and worktree (where the latter is the actual Git worktree and usually lives in the src/ subdirectory of the former). This encourages untracked and ignored files to be placed outside the worktree, making Git's job much easier. This idea, too, might find its way in one way or another into Git proper.
These are just a few concepts in Scalar that do not yet have equivalents in Git. By putting this initial implementation into contrib/, we create a foundation for future discussions of these concepts.
We plan on updating the recommended config settings in scalar register as new Git features are available (such as builtin FSMonitor and sparse-index, when ready). To facilitate upgrading existing Scalar enlistments, their paths are automatically added to the [scalar] section of the global Git config, and the scalar reconfigure --all command will process all of them.
Epilogue
========
Now, to address some questions that I imagine every reader has who made it this far:
* Why not put the Scalar functionality directly into a built-in? Creating a Git builtin requires scrutiny over every aspect of the feature, which is difficult to do while also maintaining the command-line interface contract and expected behavior of the Scalar command (there are existing users, after all). By having the Scalar command in contrib/, we present a simple option for users to have these features in the short term while the Git contributor community decides which bits to absorb into Git built-ins.
* Why implement the Scalar command in the Git codebase? We ported Scalar to the microsoft/git fork for several reasons. First, we realized it was possible now that the core features exist inside Git itself. Second, compiling Scalar directly within a version of Git allows us to remove a version compatibility check from each config option that might or might not apply based on the installed Git version. Finally, this new location has greatly simplified our release process and the installation process for users. We now have ways to install Scalar with microsoft/git via winget, brew, and apt-get. This has been the case since we shipped v2.32.0 to our users, read: this setup has served us well already.
* Why contribute Scalar to the Git project? We are biased, of course, yet we do have evidence that the Scalar command is a helpful tool that offers an simple way to handle huge repositories with ease. By contributing it to the core Git project, we are able to share it with more users, especially some users who do not want to install the microsoft/git fork. We intend to include Scalar as a component in git-for-windows/git, but are contributing it here first. Further, we think there is benefit to the Git developer community as this presents an example of how to set certain defaults that work for large repositories.
* Does this integrate with the built-in FSMonitor yet? No, not yet. I do have a couple of add-on patch series lined up, one of them being the integration with the built-in FSMonitor, which obviously has to wait until the FSMonitor patch series advances further.
Changes since v2:
* Adjusted the description of the list command in the manual page , as suggested by Bagas.
* Addressed two style nits in cmd_run().
* The documentation of git reconfigure -a was improved.
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with Scalar
Derrick Stolee (4):
scalar: 'register' sets recommended config and starts maintenance
scalar: 'unregister' stops background maintenance
scalar: implement 'scalar list'
scalar: implement the run command
Johannes Schindelin (10):
scalar: create a rudimentary executable
scalar: start documenting the command
scalar: create test infrastructure
scalar: let 'unregister' handle a deleted enlistment directory
gracefully
scalar: implement the clone subcommand
scalar: teach 'clone' to support the --single-branch option
scalar: allow reconfiguring an existing enlistment
scalar: teach 'reconfigure' to optionally handle all registered
enlistments
scalar: implement the version command
scalar: accept -C and -c options before the subcommand
Matthew John Cheetham (1):
scalar: implement the delete command
Makefile | 8 + contrib/scalar/.gitignore | 5 + contrib/scalar/Makefile | 57 +++ contrib/scalar/scalar.c | 844 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 154 ++++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 ++++ 7 files changed, 1234 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c create mode 100644 contrib/scalar/scalar.txt create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
base-commit: ebf3c04b262aa27fbb97f8a0156c2347fecafafb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1005%2Fdscho%2Fscalar-the-beginning-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v2:
1: b8c7d3f8450 = 1: b8c7d3f8450 scalar: create a rudimentary executable
2: 4f886575dcf = 2: 4f886575dcf scalar: start documenting the command
3: bcfde9bc765 = 3: bcfde9bc765 scalar: create test infrastructure
4: ee3e26a0c4e = 4: ee3e26a0c4e scalar: 'register' sets recommended config and starts maintenance
5: 6142f75875b = 5: 6142f75875b scalar: 'unregister' stops background maintenance
6: 82dd253154f = 6: 82dd253154f scalar: let 'unregister' handle a deleted enlistment directory gracefully
7: fb7c931ddb3 ! 7: d291d3723a6 scalar: implement 'scalar list'
@@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
+~~~~
+
+list::
-+ To see which repositories are currently registered by the service, run
-+ `scalar list`. This subcommand does not need to be run inside a Scalar
-+ enlistment.
++ List enlistments that are currently registered by Scalar. This
++ subcommand does not need to be run inside an enlistment.
+
Register
~~~~~~~~ 8: f3223c10788 ! 8: 40dbf61771e scalar: implement the clone subcommand
@@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
List
~~~~
- list::
- To see which repositories are currently registered by the service, run
-- scalar list. This subcommand does not need to be run inside a Scalar
-- enlistment.
-+ scalar list. This subcommand, like clone, does not need to be run
-+ inside a Scalar enlistment.
-
- Register
- ~~~~~~~~
@@ contrib/scalar/scalar.txt: unregister [<enlistment>]::
SEE ALSO
9: b3c4b3dccc6 = 9: 414dbe7d859 scalar: teach 'clone' to support the --single-branch option
10: b7fc2dc29c8 ! 10: 76de416a643 scalar: implement the `run` command
@@ contrib/scalar/scalar.c: static int cmd_register(int argc, const char **argv)
+ argc = parse_options(argc, argv, NULL, options,
+ usagestr, 0);
+
-+ if (argc == 0)
++ if (!argc)
+ usage_with_options(usagestr, options);
+
-+ if (!strcmp("all", argv[0]))
++ if (!strcmp("all", argv[0])) {
+ i = -1;
-+ else {
++ } else {
+ for (i = 0; tasks[i].arg && strcmp(tasks[i].arg, argv[0]); i++)
+ ; /* keep looking for the task */
+
11: 9a834c23d08 = 11: 655a902b9df scalar: allow reconfiguring an existing enlistment
12: 79e9f5d203a ! 12: 2d1987bfcda scalar: teach 'reconfigure' to optionally handle all registered enlistments @@ contrib/scalar/scalar.txt: After a Scalar upgrade, or when the configuration of
reconfigure the enlistment.
+With the --all option, all enlistments currently registered with Scalar
-+will be reconfigured. This option is meant to to be run every time after -+Scalar is upgraded.
++will be reconfigured. Use this option after each Scalar upgrade.
+
SEE ALSO
--------
13: 94a21982652 ! 13: c67938299ee scalar: implement the delete command
@@ contrib/scalar/scalar.txt: scalar register [<enlistment>]
DESCRIPTION
-----------
-@@ contrib/scalar/scalar.txt: With the --all option, all enlistments currently registered with Scalar
- will be reconfigured. This option is meant to to be run every time after
- Scalar is upgraded.
+@@ contrib/scalar/scalar.txt: reconfigure the enlistment.
+ With the --all option, all enlistments currently registered with Scalar
+ will be reconfigured. Use this option after each Scalar upgrade.
+Delete
+~~~~~~
14: 707d8e19683 = 14: d2cd2b7094b scalar: implement the `version` command 15: 26e23b5c5e5 = 15: 7ccc4f8b9b0 scalar: accept -C and -c options before the subcommand
Signature · 1 line
gitgitgadget
PATCH v3 1/15 scalar: create a rudimentary executable
From: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
With this patch, we start the journey from that C# project to move what is left to Git's own contrib/ directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It was always to plan to contribute all of the proven strategies back to core Git.
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
With partial clone, sparse checkout and git maintenance having been upstreamed, there is little left that scalar.exe does that which git.exe cannot do. One such thing is that scalar clone <url> will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the scalar executable and put it into the contrib/scalar/ subdirectory.
The slightly awkward addition of the contrib/scalar/* bits to the top-level Makefile are actually really required: we want to link to libgit.a, which means that we will need to use the very same CFLAGS and LDFLAGS as the rest of Git.
An early development version of this patch tried to replicate all the conditional code in contrib/scalar/Makefile (e.g. NO_POLL) just like contrib/svn-fe/Makefile used to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disabling compat/poll/, compat/regex/, compat/win32mmap.c & friends depending on the current platform... To put it mildly: it was a major mess.
Instead, this patch makes minimal changes to the top-level Makefile so that the bits in contrib/scalar/ can be compiled and linked, and adds a contrib/scalar/Makefile that uses the top-level Makefile in a most minimal way to do the actual compiling.
Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +8 -0
@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
contrib/scalar/.gitignore new file +2 -0
@@ -0,0 +1,2 @@ +/*.exe +/scalar
contrib/scalar/Makefile new file +34 -0
@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCE
contrib/scalar/scalar.c new file +36 -0
@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +}
Signature · 1 line
gitgitgadget
PATCH v3 2/15 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This commit establishes the infrastructure to build the manual page for the scalar command.
--- contrib/scalar/.gitignore | 3 +++ contrib/scalar/Makefile | 14 +++++++++++++- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 contrib/scalar/scalar.txt
contrib/scalar/.gitignore +3 -0
@@ -1,2 +1,5 @@ +/*.xml +/*.1 +/*.html /*.exe /scalar
contrib/scalar/Makefile +13 -1
@@ -6,6 +6,7 @@ ifndef V QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir + QUIET = @ else export V endif
@@ -30,5 +31,16 @@ $(TARGETS): $(GITLIBS) scalar.c clean: $(RM) $(TARGETS) + $(RM) scalar.1 scalar.html scalar.xml -.PHONY: all clean FORCE +docs: scalar.html scalar.1 + +scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel + +scalar.html scalar.1: scalar.txt + $(QUIET_SUBDIR0)../../Documentation$(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/scalar/scalar.txt \ + ../contrib/scalar/$@ + $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . + +.PHONY: all clean docs FORCE
contrib/scalar/scalar.txt new file +38 -0
@@ -0,0 +1,38 @@ +scalar(1) +========= + +NAME +---- +scalar - an opinionated repository management tool + +SYNOPSIS +-------- +[verse] +scalar <command> [<options>] + +DESCRIPTION +----------- + +Scalar is an opinionated repository management tool. By creating new +repositories or registering existing repositories with Scalar, your Git +experience will speed up. Scalar sets advanced Git config settings, +maintains your repositories in the background, and helps reduce data sent +across the network. + +An important Scalar concept is the enlistment: this is the top-level directory +of the project. It usually contains the subdirectory `src/` which is a Git +worktree. This encourages the separation between tracked files (inside `src/`) +and untracked files, such as build artifacts (outside `src/`). When registering +an existing Git worktree with Scalar whose name is not `src`, the enlistment +will be identical to the worktree. + +The `scalar` command implements various subcommands, and different options +depending on the subcommand. + +SEE ALSO +-------- +linkgit:git-maintenance[1]. + +Scalar +--- +Associated with the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
PATCH v3 3/15 scalar: create test infrastructure
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
To test the Scalar command, create a test script in contrib/scalar/t that is executed as make -C contrib/scalar test. Since Scalar has no meaningful capabilities yet, the only test is rather simple. We will add more tests in subsequent commits that introduce corresponding, new functionality.
Note: this test script is intended to test scalar only lightly, even after all of the functionality is implemented.
A more comprehensive functional (or: integration) test suite can be found at https://github.com/microsoft/scalar; It is used in the workflow
https://github.com/microsoft/git/blob/HEAD/.github/workflows/scalar-functional-tests.yml
in Microsoft's Git fork. This test suite performs end-to-end tests with a real remote repository, and is run as part of the regular CI builds. Since those tests require some functionality supported only by Microsoft's Git fork ("GVFS protocol"), there is no intention to port that fuller test suite to contrib/scalar/.
--- contrib/scalar/Makefile | 17 +++++-- contrib/scalar/t/Makefile | 78 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 17 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
contrib/scalar/Makefile +14 -3
@@ -3,6 +3,7 @@ QUIET_SUBDIR1 = ifneq ($(findstring s,$(MAKEFLAGS)),s) ifndef V + QUIET_GEN = @echo ' ' GEN $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -21,7 +22,7 @@ include ../../config.mak.uname TARGETS = scalar$(X) scalar.o GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a -all: scalar$X +all: scalar$X ../../bin-wrappers/scalar $(GITLIBS): $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ -30,9 +31,19 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar $(RM) scalar.1 scalar.html scalar.xml +../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile + @mkdir -p ../../bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell cd ../.. && pwd)|' \ + -e 's|@@PROG@@|contrib/scalar/scalar$(X)|' < $< > $@ && \ + chmod +x $@ + +test: all + $(MAKE) -C t + docs: scalar.html scalar.1 scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
@@ -43,4 +54,4 @@ scalar.html scalar.1: scalar.txt ../contrib/scalar/$@ $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . -.PHONY: all clean docs FORCE +.PHONY: all clean docs test FORCE
contrib/scalar/t/Makefile new file +78 -0
78 lines not on this page · raw message
contrib/scalar/t/t9099-scalar.sh new file +17 -0
@@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='test the `scalar` command' + +TEST_DIRECTORY=$PWD/../../../t +export TEST_DIRECTORY + +# Make it work with --no-bin-wrappers +PATH=$PWD/..:$PATH + +. ../../../t/test-lib.sh + +test_expect_success 'scalar shows a usage' ' + test_expect_code 129 scalar -h +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v3 4/15 scalar: 'register' sets recommended config and starts maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Let's start implementing the register command. With this commit, recommended settings are configured upon scalar register, and Git's background maintenance is started.
The recommended config settings may very well change in the future. For example, once the built-in FSMonitor is available, we will want to enable it upon scalar register. For that reason, we explicitly support running scalar register in an already-registered enlistment.
--- contrib/scalar/scalar.c | 255 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 272 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +255 -0
@@ -5,11 +5,266 @@#include "cache.h" #include "gettext.h" #include "parse-options.h" +#include "config.h" +#include "run-command.h" + +/* +* Remove the deepest subdirectory in the provided path string. Path must not +* include a trailing path separator. Returns 1 if parent directory found, +* otherwise 0. +*/ +static int strbuf_parent_directory (struct strbuf *buf) +{ +size_t len = buf->len; +size_t offset =offset_1st_component (buf->buf); +char *path_sep =find_last_dir_sep (buf->buf + offset); +strbuf_setlen (buf, path_sep ? path_sep - buf->buf : offset); + +return buf->len < len; +} + +static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); +do { +const size_t len = path.len; + +/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + } + +/* reset to original path */ +strbuf_setlen (&path, len); + +/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) { +strbuf_setlen (&path, len); + +if (enlistment_root) { +/* +* If the worktree's directory's name is `src`, the enlistment is the +* parent directory, otherwise it is identical to the worktree. +*/ + root =strip_path_suffix (path.buf,"src" ); +strbuf_addstr (enlistment_root, root ? root : path.buf); +free (root); + } + + enlistment_found =1 ; +break ; + } + +strbuf_setlen (&path, len); + }while (strbuf_parent_directory (&path)); + +if (!enlistment_found) +die (_ ("could not find enlistment root" )); + +if (chdir (path.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), path.buf); + +strbuf_release (&path); +setup_git_directory (); +} + +static int run_git (const char *arg, ...) +{ +struct strvec argv =STRVEC_INIT ; +va_list args; +const char *p; +int res; + +va_start (args, arg); +strvec_push (&argv, arg); + while ((p = va_arg(args, const char *))) +strvec_push (&argv, p); +va_end (args); + + res =run_command_v_opt (argv.v,RUN_GIT_CMD ); + +strvec_clear (&argv); +return res; +} + +static int set_recommended_config (void ) +{ +struct { +const char *key; +const char *value; + } config[] = { + {"am.keepCR" ,"true" }, + {"core.FSCache" ,"true" }, + {"core.multiPackIndex" ,"true" }, + {"core.preloadIndex" ,"true" }, +#ifndef WIN32 + {"core.untrackedCache" ,"true" }, +#else +/* +* Unfortunately, Scalar's Functional Tests demonstrated +* that the untracked cache feature is unreliable on Windows +* (which is a bummer because that platform would benefit the +* most from it). For some reason, freshly created files seem +* not to update the directory's `lastModified` time +* immediately, but the untracked cache would need to rely on +* that. +* +* Therefore, with a sad heart, we disable this very useful +* feature on Windows. +*/ + {"core.untrackedCache" ,"false" }, +#endif + {"core.logAllRefUpdates" ,"true" }, + {"credential.https://dev.azure.com.useHttpPath" ,"true" }, + {"credential.validate" ,"false" },/* GCM4W-only */ + {"gc.auto" ,"0" }, + {"gui.GCWarning" ,"false" }, + {"index.threads" ,"true" }, + {"index.version" ,"4" }, + {"merge.stat" ,"false" }, + {"merge.renames" ,"false" }, + {"pack.useBitmaps" ,"false" }, + {"pack.useSparse" ,"true" }, + {"receive.autoGC" ,"false" }, + {"reset.quiet" ,"true" }, + {"feature.manyFiles" ,"false" }, + {"feature.experimental" ,"false" }, + {"fetch.unpackLimit" ,"1" }, + {"fetch.writeCommitGraph" ,"false" }, +#ifdef WIN32 + {"http.sslBackend" ,"schannel" }, +#endif + {"status.aheadBehind" ,"false" }, + {"commitGraph.generationVersion" ,"1" }, + {"core.autoCRLF" ,"false" }, + {"core.safeCRLF" ,"false" }, + {NULL ,NULL }, + }; +int i; +char *value; + +for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + } + } + +/* +* The `log.excludeDecoration` setting is special because it allows +* for multiple values. +*/ +if (git_config_get_string ("log.excludeDecoration" , &value)) { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"created" ); +if (git_config_set_multivar_gently ("log.excludeDecoration" , +"refs/prefetch/*" , +CONFIG_REGEX_NONE ,0 )) +return error (_ ("could not configure " +"log.excludeDecoration" )); + }else { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"exists" ); +free (value); + } + +return 0 ; +} + +static int start_maintenance (void ) +{ +return run_git ("maintenance" ,"start" ,NULL ); +} + +static int add_enlistment (void ) +{ +int res; + +if (!the_repository->worktree) +die (_ ("Scalar enlistments require a worktree" )); + + res =run_git ("config" ,"--global" ,"--get" ,"--fixed-value" , +"scalar.repo" , the_repository->worktree,NULL ); + +/* +* If the setting is already there, then do nothing. +*/ +if (!res) +return 0 ; + +return run_git ("config" ,"--global" ,"--add" , +"scalar.repo" , the_repository->worktree,NULL ); +} + +static int register_dir (void ) +{ +int res =add_enlistment (); + +if (!res) + res =set_recommended_config (); + +if (!res) + res =start_maintenance (); + +return res; +} + +static int cmd_register (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar register [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return register_dir (); +}static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + {"register" , cmd_register }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +17 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar <command> [<options>] +scalar register [<enlistment>] DESCRIPTION -----------
@@ -29,6 +29,22 @@ will be identical to the worktree. The `scalar` command implements various subcommands, and different options depending on the subcommand. +COMMANDS +-------- + +Register +~~~~~~~~ + +register [<enlistment>]:: + Adds the enlistment's repository to the list of registered repositories + and starts background maintenance. If `<enlistment>` is not provided, + then the enlistment associated with the current working directory is + registered. ++ +Note: when this subcommand is called in a worktree that is called `src/`, its +parent directory is considered to be the Scalar enlistment. If the worktree is +_not_ called `src/`, it itself will be considered to be the Scalar enlistment. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v3 5/15 scalar: 'unregister' stops background maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Just like scalar register starts the scheduled background maintenance, scalar unregister stops it. Note that we use git maintenance start in scalar register, but we do not use git maintenance stop in scalar unregister: this would stop maintenance for _all_ repositories, not just for the one we want to unregister.
The unregister command also removes the corresponding entry from the [scalar] section in the global Git config.
--- contrib/scalar/scalar.c | 50 ++++++++++++++++++++++++++++++++------- contrib/scalar/scalar.txt | 8 +++++++ 2 files changed, 50 insertions(+), 8 deletions(-)
contrib/scalar/scalar.c +42 -8
@@ -204,12 +204,12 @@ static int set_recommended_config(void)return 0 ; } -static int start_maintenance (void ) +static int toggle_maintenance (int enable) { -return run_git ("maintenance" ,"start" ,NULL ); +return run_git ("maintenance" , enable ?"start" :"unregister" ,NULL ); } -static int add_enlistment (void ) +static int add_or_remove_enlistment (int add) {int res;
@@ -220,24 +220,39 @@ static int add_enlistment(void) "scalar.repo", the_repository->worktree, NULL);/* -* If the setting is already there, then do nothing. +* If we want to add and the setting is already there, then do nothing. +* If we want to remove and the setting is not there, then do nothing. */ -if (!res) +if ((add && !res) || (!add && res))return 0 ; -return run_git ("config" ,"--global" ,"--add" , +return run_git ("config" ,"--global" , add ?"--add" :"--unset" , + add ?"--no-fixed-value" :"--fixed-value" ,"scalar.repo" , the_repository->worktree,NULL ); }static int register_dir (void ) { -int res =add_enlistment (); +int res =add_or_remove_enlistment (1 );if (!res) res =set_recommended_config ();if (!res) - res =start_maintenance (); + res =toggle_maintenance (1 ); + +return res; +} + +static int unregister_dir (void ) +{ +int res =0 ; + +if (toggle_maintenance (0 ) <0 ) + res =-1 ; + +if (add_or_remove_enlistment (0 ) <0 ) + res =-1 ;return res; }
@@ -260,11 +275,30 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_unregister (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar unregister [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return unregister_dir (); +} +static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { {"register" , cmd_register }, + {"unregister" , cmd_unregister }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +8 -0
@@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] scalar register [<enlistment>] +scalar unregister [<enlistment>] DESCRIPTION -----------
@@ -45,6 +46,13 @@ Note: when this subcommand is called in a worktree that is called `src/`, its parent directory is considered to be the Scalar enlistment. If the worktree is _not_ called `src/`, it itself will be considered to be the Scalar enlistment. +Unregister +~~~~~~~~~~ + +unregister [<enlistment>]:: + Remove the specified repository from the list of repositories + registered with Scalar and stop the scheduled background maintenance. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v3 6/15 scalar: let 'unregister' handle a deleted enlistment directory gracefully
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
When a user deleted an enlistment manually, let's be generous and _still_ unregister it.
--- contrib/scalar/scalar.c | 46 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 15 +++++++++++ 2 files changed, 61 insertions(+)
contrib/scalar/scalar.c +46 -0
@@ -275,6 +275,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int remove_deleted_enlistment (struct strbuf *path) +{ +int res =0 ; +strbuf_realpath_forgiving (path, path->buf,1 ); + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"scalar.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"maintenance.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +return res; +} +static int cmd_unregister (int argc,const char **argv) {struct option options[] = {
@@ -288,6 +306,34 @@ static int cmd_unregister(int argc, const char **argv) argc =parse_options (argc, argv,NULL , options, usage,0 ); +/* +* Be forgiving when the enlistment or worktree does not even exist any +* longer; This can be the case if a user deleted the worktree by +* mistake and _still_ wants to unregister the thing. +*/ +if (argc ==1 ) { +struct strbuf src_path =STRBUF_INIT , workdir_path =STRBUF_INIT ; + +strbuf_addf (&src_path,"%s/src/.git" , argv[0 ]); +strbuf_addf (&workdir_path,"%s/.git" , argv[0 ]); +if (!is_directory (src_path.buf) && !is_directory (workdir_path.buf)) { +/* remove possible matching registrations */ +int res =-1 ; + +strbuf_strip_suffix (&src_path,"/.git" ); + res =remove_deleted_enlistment (&src_path) && res; + +strbuf_strip_suffix (&workdir_path,"/.git" ); + res =remove_deleted_enlistment (&workdir_path) && res; + +strbuf_release (&src_path); +strbuf_release (&workdir_path); +return res; + } +strbuf_release (&src_path); +strbuf_release (&workdir_path); + } +setup_enlistment_directory (argc, argv, usage, options,NULL );return unregister_dir ();
contrib/scalar/t/t9099-scalar.sh +15 -0
@@ -14,4 +14,19 @@ test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h ' +test_expect_success 'scalar unregister' ' + git init vanish/src && + scalar register vanish/src && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/vanish/src" scalar.repos && + rm -rf vanish/src/.git && + scalar unregister vanish && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/vanish/src" scalar.repos +' + test_done
Signature · 1 line
gitgitgadget
PATCH v3 7/15 scalar: implement 'scalar list'
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The produced list simply consists of those repositories registered under the multi-valued scalar.repo config setting in the user's Git config.
--- contrib/scalar/scalar.c | 11 +++++++++++ contrib/scalar/scalar.txt | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +11 -0
@@ -257,6 +257,16 @@ static int unregister_dir(void)return res; } +static int cmd_list (int argc,const char **argv) +{ +if (argc !=1 ) +die (_ ("`scalar list` does not take arguments" )); + +if (run_git ("config" ,"--global" ,"--get-all" ,"scalar.repo" ,NULL ) <0 ) +return -1 ; +return 0 ; +} +static int cmd_register (int argc,const char **argv) {struct option options[] = {
@@ -343,6 +353,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, { NULL, NULL},
contrib/scalar/scalar.txt +10 -1
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -28,11 +29,19 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. +depending on the subcommand. With the exception of `list`, all subcommands +expect to be run in an enlistment. COMMANDS -------- +List +~~~~ + +list:: + List enlistments that are currently registered by Scalar. This + subcommand does not need to be run inside an enlistment. + Register ~~~~~~~~
Signature · 1 line
gitgitgadget
PATCH v3 8/15 scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated clone command: it tries to use a partial clone and sets up a sparse checkout by default. In contrast to git clone, scalar clone sets up the worktree in the src/ subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Also, it registers the repository for regular, scheduled maintenance, and configures a flurry of configuration settings based on the experience and experiments of the Microsoft Windows and the Microsoft Office development teams.
Note: since the scalar clone command is by far the most commonly called scalar subcommand, we document it at the top of the manual page.
--- contrib/scalar/scalar.c | 201 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 31 ++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 261 insertions(+), 3 deletions(-)
contrib/scalar/scalar.c +201 -0
@@ -7,6 +7,7 @@#include "parse-options.h" #include "config.h" #include "run-command.h" +#include "refs.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -257,6 +258,205 @@ static int unregister_dir(void)return res; } +/* printf-style interface, expects `<key>=<value>` argument */ +static int set_config (const char *fmt, ...) +{ +struct strbuf buf =STRBUF_INIT ; +char *value; +int res; +va_list args; + +va_start (args, fmt); +strbuf_vaddf (&buf, fmt, args); +va_end (args); + + value =strchr (buf.buf,'=' ); +if (value) + *(value++) ='\0' ; + res =git_config_set_gently (buf.buf, value); +strbuf_release (&buf); + +return res; +} + +static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +const char *line = out.buf; + +while (*line) { +const char *eol =strchrnul (line,'\n' ), *p; +size_t len = eol - line; +char *branch; + +if (!skip_prefix (line,"ref: " , &p) || + !strip_suffix_mem (line, &len,"\tHEAD" )) { + line = eol + (*eol =='\n' ); +continue ; + } + + eol = line + len; +if (skip_prefix (p,"refs/heads/" , &p)) { + branch =xstrndup (p, eol - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(eol - p), p); +strbuf_release (&out); +return NULL ; + } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} + +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b); + } + +if ((res =run_git ("-c" , buf.buf,"init" ,"--" , dir,NULL ))) + goto cleanup; + +if (chdir (dir) <0 ) { + res =error_errno (_ ("could not switch to '%s'" ), dir); + goto cleanup; + } + +setup_git_directory (); + +/* common-main already logs `argv` */ +trace2_def_repo (the_repository); + +if (!branch && !(branch =remote_default_branch (url))) { + res =error (_ ("failed to get default branch for '%s'" ), url); + goto cleanup; + } + +if (set_config ("remote.origin.url=%s" , url) || +set_config ("remote.origin.fetch=" +"+refs/heads/*:refs/remotes/origin/*" ) || +set_config ("remote.origin.promisor=true" ) || +set_config ("remote.origin.partialCloneFilter=blob:none" )) { + res =error (_ ("could not configure remote in '%s'" ), dir); + goto cleanup; + } + +if (!full_clone && + (res =run_git ("sparse-checkout" ,"init" ,"--cone" ,NULL ))) + goto cleanup; + +if (set_recommended_config ()) +return error (_ ("could not configure '%s'" ), dir); + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) { +warning (_ ("partial clone failed; attempting full clone" )); + +if (set_config ("remote.origin.promisor" ) || +set_config ("remote.origin.partialCloneFilter" )) { + res =error (_ ("could not configure for full clone" )); + goto cleanup; + } + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) + goto cleanup; + } + +if ((res =set_config ("branch.%s.remote=origin" , branch))) + goto cleanup; +if ((res =set_config ("branch.%s.merge=refs/heads/%s" , + branch, branch))) + goto cleanup; + +strbuf_reset (&buf); +strbuf_addf (&buf,"origin/%s" , branch); + res =run_git ("checkout" ,"-f" ,"-t" , buf.buf,NULL ); +if (res) + goto cleanup; + + res =register_dir (); + +cleanup: +free (enlistment); +free (dir); +strbuf_release (&buf); +return res; +} +static int cmd_list (int argc,const char **argv) {if (argc !=1 )
@@ -353,6 +553,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "clone", cmd_clone }, { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister },
contrib/scalar/scalar.txt +28 -3
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -29,12 +30,36 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `list`, all subcommands -expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone` and `list`, all +subcommands expect to be run in an enlistment. COMMANDS -------- +Clone +~~~~~ + +clone [<options>] <url> [<enlistment>]:: + Clones the specified repository, similar to linkgit:git-clone[1]. By + default, only commit and tree objects are cloned. Once finished, the + worktree is located at `<enlistment>/src`. ++ +The sparse-checkout feature is enabled (except when run with `--full-clone`) +and the only files present are those in the top-level directory. Use +`git sparse-checkout set` to expand the set of directories you want to see, +or `git sparse-checkout disable` to expand to all files (see +linkgit:git-sparse-checkout[1] for more details). You can explore the +subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. + +-b <name>:: +--branch <name>:: + Instead of checking out the branch pointed to by the cloned + repository's HEAD, check out the `<name>` branch instead. + +--[no-]full-clone:: + A sparse-checkout is initialized by default. This behavior can be + turned off via `--full-clone`. + List ~~~~
@@ -64,7 +89,7 @@ unregister [<enlistment>]:: SEE ALSO -------- -linkgit:git-maintenance[1]. +linkgit:git-clone[1], linkgit:git-maintenance[1]. Scalar ---
contrib/scalar/t/t9099-scalar.sh +32 -0
@@ -10,6 +10,9 @@ PATH=$PWD/..:$PATH . ../../../t/test-lib.sh +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt" +export GIT_TEST_MAINT_SCHEDULER + test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h '
@@ -29,4 +32,33 @@ test_expect_success 'scalar unregister' ' ! grep -F "$(pwd)/vanish/src" scalar.repos ' +test_expect_success 'set up repository to clone' ' + test_commit first && + test_commit second && + test_commit third && + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + git config uploadPack.allowFilter true && + git config uploadPack.allowAnySHA1InWant true +' + +test_expect_success 'scalar clone' ' + second=$(git rev-parse --verify second:second.t) && + scalar clone "file://$(pwd)" cloned && + ( + cd cloned/src && + + git config --get --global --fixed-value maintenance.repo \ + "$(pwd)" && + + test_path_is_missing 1/2 && + test_must_fail git rev-list --missing=print $second && + git rev-list $second && + git cat-file blob $second >actual && + echo "second" >expect && + test_cmp expect actual + ) +' + test_done
Signature · 1 line
gitgitgadget
PATCH v3 9/15 scalar: teach 'clone' to support the --single-branch option
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like git clone, the scalar clone command now also offers to restrict the clone to a single branch.
--- contrib/scalar/scalar.c | 9 +++++++-- contrib/scalar/scalar.txt | 12 +++++++++++- contrib/scalar/t/t9099-scalar.sh | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-)
contrib/scalar/scalar.c +7 -2
@@ -333,12 +333,15 @@ static char *remote_default_branch(const char *url)static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ; -int full_clone =0 ; +int full_clone =0 , single_branch =0 ;struct option clone_options[] = {OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ),N_ ("branch to checkout after clone" )),OPT_BOOL (0 ,"full-clone" , &full_clone,N_ ("when cloning, create full working directory" )), +OPT_BOOL (0 ,"single-branch" , &single_branch, +N_ ("only download metadata for the branch that will " +"be checked out" )),OPT_END (), };const char *const clone_usage[] = {
@@ -409,7 +412,9 @@ static int cmd_clone(int argc, const char **argv)if (set_config ("remote.origin.url=%s" , url) ||set_config ("remote.origin.fetch=" -"+refs/heads/*:refs/remotes/origin/*" ) || +"+refs/heads/%s:refs/remotes/origin/%s" , + single_branch ? branch :"*" , + single_branch ? branch :"*" ) ||set_config ("remote.origin.promisor=true" ) ||set_config ("remote.origin.partialCloneFilter=blob:none" )) { res =error (_ ("could not configure remote in '%s'" ), dir);
contrib/scalar/scalar.txt +11 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] +scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -56,6 +56,16 @@ subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. Instead of checking out the branch pointed to by the cloned repository's HEAD, check out the `<name>` branch instead. +--[no-]single-branch:: + Clone only the history leading to the tip of a single branch, either + specified by the `--branch` option or the primary branch remote's + `HEAD` points at. ++ +Further fetches into the resulting repository will only update the +remote-tracking branch for the branch this option was used for the initial +cloning. If the HEAD at the remote did not point at any branch when +`--single-branch` clone was made, no remote-tracking branch is created. + --[no-]full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`.
contrib/scalar/t/t9099-scalar.sh +5 -1
@@ -45,13 +45,17 @@ test_expect_success 'set up repository to clone' ' test_expect_success 'scalar clone' ' second=$(git rev-parse --verify second:second.t) && - scalar clone "file://$(pwd)" cloned && + scalar clone "file://$(pwd)" cloned --single-branch && ( cd cloned/src && git config --get --global --fixed-value maintenance.repo \ "$(pwd)" && + git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual && + echo "refs/remotes/origin/parallel" >expect && + test_cmp expect actual && + test_path_is_missing 1/2 && test_must_fail git rev-list --missing=print $second && git rev-list $second &&
Signature · 1 line
gitgitgadget
PATCH v3 10/15 scalar: implement the `run` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Note: this subcommand is provided primarily for backwards-compatibility, for existing Scalar uses. It is mostly just a shim for git maintenance, mapping task names from the way Scalar called them to the way Git calls them.
The reason why those names differ? The background maintenance was first implemented in Scalar, and when it was contributed as a patch series implementing the git maintenance command, reviewers suggested better names, those suggestions were accepted before the patches were integrated into core Git.
--- contrib/scalar/scalar.c | 64 +++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 19 ++++++++++++ 2 files changed, 83 insertions(+)
contrib/scalar/scalar.c +64 -0
@@ -490,6 +490,69 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_run (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +struct { +const char *arg, *task; + } tasks[] = { + {"config" ,NULL }, + {"commit-graph" ,"commit-graph" }, + {"fetch" ,"prefetch" }, + {"loose-objects" ,"loose-objects" }, + {"pack-files" ,"incremental-repack" }, + {NULL ,NULL } + }; +struct strbuf buf =STRBUF_INIT ; +const char *usagestr[] = {NULL ,NULL }; +int i; + +strbuf_addstr (&buf,N_ ("scalar run <task> [<enlistment>]\nTasks:\n" )); +for (i =0 ; tasks[i].arg; i++) +strbuf_addf (&buf,"\t%s\n" , tasks[i].arg); + usagestr[0 ] = buf.buf; + + argc =parse_options (argc, argv,NULL , options, + usagestr,0 ); + +if (!argc) +usage_with_options (usagestr, options); + +if (!strcmp ("all" , argv[0 ])) { + i =-1 ; + }else { +for (i =0 ; tasks[i].arg &&strcmp (tasks[i].arg, argv[0 ]); i++) + ;/* keep looking for the task */ + +if (i >0 && !tasks[i].arg) { +error (_ ("no such task: '%s'" ), argv[0 ]); +usage_with_options (usagestr, options); + } + } + + argc--; + argv++; +setup_enlistment_directory (argc, argv, usagestr, options,NULL ); +strbuf_release (&buf); + +if (i ==0 ) +return register_dir (); + +if (i >0 ) +return run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL ); + +if (register_dir ()) +return -1 ; +for (i =1 ; tasks[i].arg; i++) +if (run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL )) +return -1 ; +return 0 ; +} +static int remove_deleted_enlistment (struct strbuf *path) {int res =0 ;
@@ -562,6 +625,7 @@ static struct { { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, + { "run", cmd_run }, { NULL, NULL}, };
contrib/scalar/scalar.txt +19 -0
@@ -12,6 +12,7 @@ scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<e scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] DESCRIPTION -----------
@@ -97,6 +98,24 @@ unregister [<enlistment>]:: Remove the specified repository from the list of repositories registered with Scalar and stop the scheduled background maintenance. +Run +~~~ + +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]:: + Run the given maintenance task (or all tasks, if `all` was specified). + Except for `all` and `config`, this subcommand simply hands off to + linkgit:git-maintenance[1] (mapping `fetch` to `prefetch` and + `pack-files` to `incremental-repack`). ++ +These tasks are run automatically as part of the scheduled maintenance, +as soon as the repository is registered with Scalar. It should therefore +not be necessary to run this subcommand manually. ++ +The `config` task is specific to Scalar and configures all those +opinionated default settings that make Git work more efficiently with +large repositories. As this task is run as part of `scalar clone` +automatically, explicit invocations of this task are rarely needed. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v3 11/15 scalar: allow reconfiguring an existing enlistment
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
--- contrib/scalar/scalar.c | 79 +++++++++++++++++++++----------- contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 8 ++++ 3 files changed, 67 insertions(+), 28 deletions(-)
contrib/scalar/scalar.c +51 -28
@@ -115,18 +115,20 @@ static int run_git(const char *arg, ...)return res; } -static int set_recommended_config (void ) +static int set_recommended_config (int reconfigure) {struct {const char *key;const char *value; +int overwrite_on_reconfigure; } config[] = { - {"am.keepCR" ,"true" }, - {"core.FSCache" ,"true" }, - {"core.multiPackIndex" ,"true" }, - {"core.preloadIndex" ,"true" }, +/* Required */ + {"am.keepCR" ,"true" ,1 }, + {"core.FSCache" ,"true" ,1 }, + {"core.multiPackIndex" ,"true" ,1 }, + {"core.preloadIndex" ,"true" ,1 }, #ifndef WIN32 - {"core.untrackedCache" ,"true" }, + {"core.untrackedCache" ,"true" ,1 }, #else /* * Unfortunately, Scalar's Functional Tests demonstrated
@@ -140,28 +142,29 @@ static int set_recommended_config(void) * Therefore, with a sad heart, we disable this very useful * feature on Windows. */ - { "core.untrackedCache", "false" }, + { "core.untrackedCache", "false", 1 }, #endif - { "core.logAllRefUpdates", "true" }, - { "credential.https://dev.azure.com.useHttpPath", "true" }, - { "credential.validate", "false" }, /* GCM4W-only */ - { "gc.auto", "0" }, - { "gui.GCWarning", "false" }, - { "index.threads", "true" }, - { "index.version", "4" }, - { "merge.stat", "false" }, - { "merge.renames", "false" }, - { "pack.useBitmaps", "false" }, - { "pack.useSparse", "true" }, - { "receive.autoGC", "false" }, - { "reset.quiet", "true" }, - { "feature.manyFiles", "false" }, - { "feature.experimental", "false" }, - { "fetch.unpackLimit", "1" }, - { "fetch.writeCommitGraph", "false" }, + { "core.logAllRefUpdates", "true", 1 }, + { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, + { "credential.validate", "false", 1 }, /* GCM4W-only */ + { "gc.auto", "0", 1 }, + { "gui.GCWarning", "false", 1 }, + { "index.threads", "true", 1 }, + { "index.version", "4", 1 }, + { "merge.stat", "false", 1 }, + { "merge.renames", "false", 1 }, + { "pack.useBitmaps", "false", 1 }, + { "pack.useSparse", "true", 1 }, + { "receive.autoGC", "false", 1 }, + { "reset.quiet", "true", 1 }, + { "feature.manyFiles", "false", 1 }, + { "feature.experimental", "false", 1 }, + { "fetch.unpackLimit", "1", 1 }, + { "fetch.writeCommitGraph", "false", 1 }, #ifdef WIN32 - { "http.sslBackend", "schannel" }, + { "http.sslBackend", "schannel", 1 }, #endif + /* Optional */ { "status.aheadBehind", "false" }, { "commitGraph.generationVersion", "1" }, { "core.autoCRLF", "false" },
@@ -172,7 +175,8 @@ static int set_recommended_config(void)char *value;for (i =0 ; config[i].key; i++) { -if (git_config_get_string (config[i].key, &value)) { +if ((reconfigure && config[i].overwrite_on_reconfigure) || +git_config_get_string (config[i].key, &value)) {trace2_data_string ("scalar" , the_repository, config[i].key,"created" );if (git_config_set_gently (config[i].key, config[i].value) <0 )
@@ -237,7 +241,7 @@ static int register_dir(void)int res =add_or_remove_enlistment (1 );if (!res) - res =set_recommended_config (); + res =set_recommended_config (0 );if (!res) res =toggle_maintenance (1 );
@@ -425,7 +429,7 @@ static int cmd_clone(int argc, const char **argv) (res = run_git("sparse-checkout", "init", "--cone", NULL))) goto cleanup; -if (set_recommended_config ()) +if (set_recommended_config (0 ))return error (_ ("could not configure '%s'" ), dir);if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) {
@@ -490,6 +494,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_reconfigure (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar reconfigure [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); +} +static int cmd_run (int argc,const char **argv) {struct option options[] = {
@@ -626,6 +648,7 @@ static struct { { "register", cmd_register }, { "unregister", cmd_unregister }, { "run", cmd_run }, + { "reconfigure", cmd_reconfigure }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -13,6 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] +scalar reconfigure <enlistment> DESCRIPTION -----------
@@ -116,6 +117,13 @@ opinionated default settings that make Git work more efficiently with large repositories. As this task is run as part of `scalar clone` automatically, explicit invocations of this task are rarely needed. +Reconfigure +~~~~~~~~~~~ + +After a Scalar upgrade, or when the configuration of a Scalar enlistment +was somehow corrupted or changed by mistake, this subcommand allows to +reconfigure the enlistment. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +8 -0
@@ -65,4 +65,12 @@ test_expect_success 'scalar clone' ' ) ' +test_expect_success 'scalar reconfigure' ' + git init one/src && + scalar register one && + git -C one/src config core.preloadIndex false && + scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" +' + test_done
Signature · 1 line
gitgitgadget
PATCH v3 12/15 scalar: teach 'reconfigure' to optionally handle all registered enlistments
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
After a Scalar upgrade, it can come in really handy if there is an easy way to reconfigure all Scalar enlistments. This new option offers this functionality.
--- contrib/scalar/scalar.c | 61 ++++++++++++++++++++++++++++++-- contrib/scalar/scalar.txt | 9 +++-- contrib/scalar/t/t9099-scalar.sh | 3 ++ 3 files changed, 67 insertions(+), 6 deletions(-)
contrib/scalar/scalar.c +58 -3
@@ -494,22 +494,77 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int get_scalar_repos (const char *key,const char *value,void *data) +{ +struct string_list *list = data; + +if (!strcmp (key,"scalar.repo" )) +string_list_append (list, value); + +return 0 ; +} +static int cmd_reconfigure (int argc,const char **argv) { +int all =0 ;struct option options[] = { +OPT_BOOL ('a' ,"all" , &all, +N_ ("reconfigure all registered enlistments" )),OPT_END (), };const char *const usage[] = { -N_ ("scalar reconfigure [<enlistment>]" ), +N_ ("scalar reconfigure [--all | <enlistment>]" ),NULL }; +struct string_list scalar_repos =STRING_LIST_INIT_DUP ; +int i, res =0 ; +struct repository r = {NULL }; +struct strbuf commondir =STRBUF_INIT , gitdir =STRBUF_INIT ; argc =parse_options (argc, argv,NULL , options, usage,0 ); -setup_enlistment_directory (argc, argv, usage, options,NULL ); +if (!all) { +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); + } + +if (argc >0 ) +usage_msg_opt (_ ("--all or <enlistment>, but not both" ), + usage, options); + +git_config (get_scalar_repos, &scalar_repos); -return set_recommended_config (1 ); +for (i =0 ; i < scalar_repos.nr; i++) { +const char *dir = scalar_repos.items[i].string; + +strbuf_reset (&commondir); +strbuf_reset (&gitdir); + +if (chdir (dir) <0 ) { +warning_errno (_ ("could not switch to '%s'" ), dir); + res =-1 ; + }else if (discover_git_directory (&commondir, &gitdir) <0 ) { +warning_errno (_ ("git repository gone in '%s'" ), dir); + res =-1 ; + }else { +git_config_clear (); + + the_repository = &r; + r.commondir = commondir.buf; + r.gitdir = gitdir.buf; + +if (set_recommended_config (1 ) <0 ) + res =-1 ; + } + } + +string_list_clear (&scalar_repos,1 ); +strbuf_release (&commondir); +strbuf_release (&gitdir); + +return res; }static int cmd_run (int argc,const char **argv)
contrib/scalar/scalar.txt +6 -3
@@ -13,7 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] -scalar reconfigure <enlistment> +scalar reconfigure [ --all | <enlistment> ] DESCRIPTION -----------
@@ -32,8 +32,8 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `clone` and `list`, all -subcommands expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone`, `list` and +`reconfigure --all`, all subcommands expect to be run in an enlistment. COMMANDS --------
@@ -124,6 +124,9 @@ After a Scalar upgrade, or when the configuration of a Scalar enlistment was somehow corrupted or changed by mistake, this subcommand allows to reconfigure the enlistment. +With the `--all` option, all enlistments currently registered with Scalar +will be reconfigured. Use this option after each Scalar upgrade. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +3 -0
@@ -70,6 +70,9 @@ test_expect_success 'scalar reconfigure' ' scalar register one && git -C one/src config core.preloadIndex false && scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" && + git -C one/src config core.preloadIndex false && + scalar reconfigure -a && test true = "$(git -C one/src config core.preloadIndex)" '
Signature · 1 line
gitgitgadget
PATCH v3 13/15 scalar: implement the `delete` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Matthew John Cheetham <[email protected]>
Delete an enlistment by first unregistering the repository and then deleting the enlistment directory (usually the directory containing the worktree src/ directory).
On Windows, if the current directory is inside the enlistment's directory, change to the parent of the enlistment directory, to allow us to delete the enlistment (directories used by processes e.g. as current working directories cannot be deleted on Windows).
--- contrib/scalar/scalar.c | 55 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 +++++ contrib/scalar/t/t9099-scalar.sh | 9 ++++++ 3 files changed, 72 insertions(+)
contrib/scalar/scalar.c +55 -0
@@ -8,6 +8,7 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -334,6 +335,33 @@ static char *remote_default_branch(const char *url)return NULL ; } +static int delete_enlistment (struct strbuf *enlistment) +{ +#ifdef WIN32 +struct strbuf parent =STRBUF_INIT ; +#endif + +if (unregister_dir ()) +die (_ ("failed to unregister repository" )); + +#ifdef WIN32 +/* +* Change the current directory to one outside of the enlistment so +* that we may delete everything underneath it. +*/ +strbuf_addbuf (&parent, enlistment); +strbuf_parent_directory (&parent); +if (chdir (parent.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), parent.buf); +strbuf_release (&parent); +#endif + +if (remove_dir_recursively (enlistment,0 )) +die (_ ("failed to delete enlistment directory" )); + +return 0 ; +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -694,6 +722,32 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar delete <enlistment>" ), +NULL + }; +struct strbuf enlistment =STRBUF_INIT ; +int res =0 ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=1 ) +usage_with_options (usage, options); + +setup_enlistment_directory (argc, argv, usage, options, &enlistment); + + res =delete_enlistment (&enlistment); +strbuf_release (&enlistment); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -704,6 +758,7 @@ static struct { { "unregister", cmd_unregister }, { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, + { "delete", cmd_delete }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -14,6 +14,7 @@ scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] scalar reconfigure [ --all | <enlistment> ] +scalar delete <enlistment> DESCRIPTION -----------
@@ -127,6 +128,13 @@ reconfigure the enlistment. With the `--all` option, all enlistments currently registered with Scalar will be reconfigured. Use this option after each Scalar upgrade. +Delete +~~~~~~ + +delete <enlistment>:: + This subcommand lets you delete an existing Scalar enlistment from your + local file system, unregistering the repository. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +9 -0
@@ -76,4 +76,13 @@ test_expect_success 'scalar reconfigure' ' test true = "$(git -C one/src config core.preloadIndex)" ' +test_expect_success 'scalar delete without enlistment shows a usage' ' + test_expect_code 129 scalar delete +' + +test_expect_success 'scalar delete with enlistment' ' + scalar delete cloned && + test_path_is_missing cloned +' + test_done
Signature · 1 line
gitgitgadget
PATCH v3 14/15 scalar: implement the `version` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The .NET version of Scalar has a version command. This was necessary because it was versioned independently of Git.
Since Scalar is now tightly coupled with Git, it does not make sense for them to show different versions. Therefore, it shows the same output as git version. For backwards-compatibility with the .NET version, scalar version prints to stderr, though (git version prints to stdout instead).
--- contrib/scalar/scalar.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
contrib/scalar/scalar.c +39 -0
@@ -9,6 +9,7 @@#include "run-command.h" #include "refs.h" #include "dir.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -362,6 +363,15 @@ static int delete_enlistment(struct strbuf *enlistment)return 0 ; } +/* +* Dummy implementation; Using `get_version_info()` would cause a link error +* without this. +*/ +void load_builtin_commands (const char *prefix,struct cmdnames *cmds) +{ +die ("not implemented" ); +} +static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ;
@@ -748,6 +758,34 @@ static int cmd_delete(int argc, const char **argv)return res; } +static int cmd_version (int argc,const char **argv) +{ +int verbose =0 , build_options =0 ; +struct option options[] = { +OPT__VERBOSE (&verbose,N_ ("include Git version" )), +OPT_BOOL (0 ,"build-options" , &build_options, +N_ ("include Git's build options" )), +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar verbose [-v | --verbose] [--build-options]" ), +NULL + }; +struct strbuf buf =STRBUF_INIT ; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +if (argc !=0 ) +usage_with_options (usage, options); + +get_version_info (&buf, build_options); +fprintf (stderr,"%s\n" , buf.buf); +strbuf_release (&buf); + +return 0 ; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -759,6 +797,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
PATCH v3 15/15 scalar: accept -C and -c options before the subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The git executable has these two very useful options:
-C <directory>:
switch to the specified directory before performing any actions
-c <key>=<value>:
temporarily configure this setting for the duration of the specified scalar subcommand
With this commit, we teach the scalar executable the same trick.
--- contrib/scalar/scalar.c | 22 +++++++++++++++++++++- contrib/scalar/scalar.txt | 10 ++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +21 -1
@@ -806,6 +806,25 @@ int cmd_main(int argc, const char **argv)struct strbuf scalar_usage =STRBUF_INIT ;int i; +while (argc >1 && *argv[1 ] =='-' ) { +if (!strcmp (argv[1 ],"-C" )) { +if (argc <3 ) +die (_ ("-C requires a <directory>" )); +if (chdir (argv[2 ]) <0 ) +die_errno (_ ("could not change to '%s'" ), + argv[2 ]); + argc -=2 ; + argv +=2 ; + }else if (!strcmp (argv[1 ],"-c" )) { +if (argc <3 ) +die (_ ("-c requires a <key>=<value> argument" )); +git_config_push_parameter (argv[2 ]); + argc -=2 ; + argv +=2 ; + }else +break ; + } +if (argc >1 ) { argv++; argc--;
@@ -816,7 +835,8 @@ int cmd_main(int argc, const char **argv) }strbuf_addstr (&scalar_usage, -N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +N_ ("scalar [-C <directory>] [-c <key>=<value>] " +"<command> [<options>]\n\nCommands:\n" ));for (i =0 ; builtins[i].name; i++)strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name);
contrib/scalar/scalar.txt +10 -0
@@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options depending on the subcommand. With the exception of `clone`, `list` and `reconfigure --all`, all subcommands expect to be run in an enlistment. +The following options can be specified _before_ the subcommand: + +-C <directory>:: + Before running the subcommand, change the working directory. This + option imitates the same option of linkgit:git[1]. + +-c <key>=<value>:: + For the duration of running the specified subcommand, configure this + setting. This option imitates the same option of linkgit:git[1]. + COMMANDS --------
Signature · 1 line
gitgitgadget
Re: [PATCH v3 07/15] scalar: implement 'scalar list'
In reply to Derrick Stolee via GitGitGadgetcontrib/scalar/scalar.txt an existing Git worktree with Scalar whose name is not `src`, the enlistment · line 44
Quoted 7 lines · Derrick Stolee via GitGitGadget
contrib/scalar/scalar.txt +7
-0+List +~~~~ + +list:: + List enlistments that are currently registered by Scalar. This + subcommand does not need to be run inside an enlistment. +
Looks OK.
Signature · 1 line
An old man doll... just what I always wanted! - Clara
Quoted 7 lines · Johannes Schindelin via GitGitGadget
Changes since v2:
* Adjusted the description of the list command in the manual page , as suggested by Bagas.
* Addressed two style nits incmd_run().
* The documentation of git reconfigure -a was improved.
Quoted 6 lines
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with Scalar
In the summary I had on v1->v2 points 1-3 are for v2->v3, respectively, outstanding, addressed, outstanding:
https://lore.kernel.org/git/[email protected]/
In addition the discussion ending here:
https://lore.kernel.org/git/[email protected]/
For that point: I think it's fair enough not to properly handle the cleanup case in "scalar clone", but perhaps add a note in the commit message that unlike "git clone" this is known not to clean after itself properly on ctrl+c?
Re: [PATCH 08/15] scalar: implement the `clone` subcommand
In reply to Johannes SchindelinQuoted 11 lines · Johannes Schindelin
[...]
Which means that I want to weigh how much effort to put into polishing an unlikely code path on one side, and on the other side how much effort to put into moving the functionality away fromcontrib/and deleting that unlikely code path.In the same vein, while this patch series contains (mostly) code in
contrib/(and therefore technically does not need to adhere strictly to Git's code style), it is probably wise to pay closer attention to the code style particularly in those parts that are prone to be moved verbatim (or close to verbatim) to Git proper.
I don't think we have any such exception to our usual style & preferred code patterns in contrib/* or compat/* in general. In the latter case we have e.g. compat/regex/ and other externally-imported codebases, which we've tried to stylistically modify as little as possible to make subsequent imports easier, ditto sha1dc/ etc.
I think the general (but unwritten) rule has been to draw the distinction on whether or not code is still externally maintained and expected to be imported, or if it's expected to be maintained in git.git going forward.
I think that this proposed series falls thoroughly in the latter category, but maybe I've misunderstood it..
Also re my [1] I had some (still relevant, but unaddressed) points on v1 about how placing this in contrib/* made certain aspects of integrating it into our build system harder. I was imagining that distinction as purely an internal implementation detail to git.git (make install etc. would behave the same), but per the above it seems to come with deeper connotations than that at least in your mind.
Re: [PATCH v3 01/15] scalar: create a rudimentary executable
In reply to Johannes Schindelin via GitGitGadgetQuoted 7 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
Quoted 7 lines
With this patch, we start the journey from that C# project to move what is left to Git's own
contrib/directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It was always to plan to contribute all of the proven strategies back to core Git.
s/always to plan/always the plan/
Quoted 15 lines
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
Quoted 5 lines
With partial clone, sparse checkout and
git maintenancehaving been upstreamed, there is little left thatscalar.exedoes that whichgit.execannot do. One such thing is thatscalar clone <url>will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.
s/does that which/does which/
Quoted 161 lines
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the
scalarexecutable and put it into the contrib/scalar/ subdirectory.The slightly awkward addition of the
contrib/scalar/*bits to the top-levelMakefileare actually really required: we want to link tolibgit.a, which means that we will need to use the very sameCFLAGSandLDFLAGSas the rest of Git.An early development version of this patch tried to replicate all the conditional code in
contrib/scalar/Makefile(e.g.NO_POLL) just likecontrib/svn-fe/Makefileused to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disablingcompat/poll/,compat/regex/,compat/win32mmap.c& friends depending on the current platform... To put it mildly: it was a major mess.Instead, this patch makes minimal changes to the top-level
Makefileso that the bits incontrib/scalar/can be compiled and linked, and adds acontrib/scalar/Makefilethat uses the top-levelMakefilein a most minimal way to do the actual compiling.Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
Signed-off-by Johannes Schindelin <[email protected]>--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.cMakefile +8
-0@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^contrib/scalar/.gitignore new file +2
-0@@ -0,0 +1,2 @@ +/*.exe +/scalarcontrib/scalar/Makefile new file +34
-0@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCEcontrib/scalar/scalar.c new file +36
-1@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +} --gitgitgadget
Re: [PATCH v3 01/15] scalar: create a rudimentary executable
Hi Elijah,
Quoted 34 lines · Elijah Newren
Quoted 7 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
Quoted 7 lines
With this patch, we start the journey from that C# project to move what is left to Git's own
contrib/directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It was always to plan to contribute all of the proven strategies back to core Git.s/always to plan/always the plan/
Quoted 15 lines
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
Quoted 7 lines
Quoted 5 lines
With partial clone, sparse checkout and
git maintenancehaving been upstreamed, there is little left thatscalar.exedoes that whichgit.execannot do. One such thing is thatscalar clone <url>will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.s/does that which/does which/
Thank you!
I am holding off from sending a new iteration (with the suggested fixes) until tomorrow, waiting for more suggestions to trickle in.
Thanks,
Dscho
Quoted 163 lines
Quoted 162 lines
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the
scalarexecutable and put it into the contrib/scalar/ subdirectory.The slightly awkward addition of the
contrib/scalar/*bits to the top-levelMakefileare actually really required: we want to link tolibgit.a, which means that we will need to use the very sameCFLAGSandLDFLAGSas the rest of Git.An early development version of this patch tried to replicate all the conditional code in
contrib/scalar/Makefile(e.g.NO_POLL) just likecontrib/svn-fe/Makefileused to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disablingcompat/poll/,compat/regex/,compat/win32mmap.c& friends depending on the current platform... To put it mildly: it was a major mess.Instead, this patch makes minimal changes to the top-level
Makefileso that the bits incontrib/scalar/can be compiled and linked, and adds acontrib/scalar/Makefilethat uses the top-levelMakefilein a most minimal way to do the actual compiling.Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
Signed-off-by Johannes Schindelin <[email protected]>--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.cMakefile +8
-0@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^contrib/scalar/.gitignore new file +2
-0@@ -0,0 +1,2 @@ +/*.exe +/scalarcontrib/scalar/Makefile new file +34
-0@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCEcontrib/scalar/scalar.c new file +36
-1@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +} --gitgitgadget
Quoted 21 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Johannes Schindelin via GitGitGadget
Changes since v2:
* Adjusted the description of the list command in the manual page , as suggested by Bagas.
* Addressed two style nits incmd_run().
* The documentation of git reconfigure -a was improved.Quoted 6 lines
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with ScalarIn the summary I had on
v1->v2points 1-3 are forv2->v3, respectively, outstanding, addressed, outstanding:https://lore.kernel.org/git/[email protected]/
Quoted 7 lines
In addition the discussion ending here:
https://lore.kernel.org/git/[email protected]/For that point: I think it's fair enough not to properly handle the cleanup case in "scalar clone", but perhaps add a note in the commit message that unlike "git clone" this is known not to clean after itself properly on ctrl+c?
Seeing [1] about the planned re-roll I have the above a shot a few days ago, see the original discussion at [2] (indirectly linked above).
The dependency graph isn't quite there yet, but I basically had it working (some twiddling around adding a new top-level command name needed). Result:
.gitignore | 1 +
Documentation/Makefile | 4 ++++
Makefile | 27 +++++++++++++++++++++++++++
3 files changed, 32 insertions(+)And by comparison, your v3:
Makefile | 8 +++++
contrib/scalar/.gitignore | 5 +++
contrib/scalar/Makefile | 57 ++++++++++++++++++++++++++++++++++
contrib/scalar/t/Makefile | 78 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 148 insertions(+)So that's a very pleasing reduction in complexity.
The WIP change for that is below, some oddities like the s/scalarscalar/scalar/ (couldn't find the bit that generated that built-in blurb at the time).
But for one the automatic lint integration in Documentation/ found one nit, and "make test" etc. all work with this automatically.
All guarded behind a CONTRIB_SCALAR flag, so the end result isn't in any way different.
Again, as pointed out in [2] the proposal isn't in any way to change what an end user sees, just to make our build system less complex. Stretching dependency graphs across Makefiles is a pain.
So just as an example I was improving the "sparse" and "hdr-check" targets today, with this dropped into the main Makefile under a flag stuff like that will Just Work, if it's under its own Makefile infrastructure it'll need to be treated specially for every such change, ditto "make TAGS" etc.
1. http://lore.kernel.org/git/[email protected];
2. https://lore.kernel.org/git/[email protected]/
.gitignore +1 -0
@@ -216,6 +216,7 @@ /configure /.vscode/ /tags +/scalar /TAGS /cscope* /compile_commands.json
Documentation/Makefile +4 -0
@@ -19,6 +19,10 @@ MAN1_TXT += git.txt MAN1_TXT += gitk.txt MAN1_TXT += gitweb.txt +ifdef CONTRIB_SCALAR +MAN1_TXT += scalar.txt +endif + # man5 / man7 guides (note: new guides should also be added to command-list.txt) MAN5_TXT += gitattributes.txt MAN5_TXT += githooks.txt
Makefile +27 -0
@@ -584,6 +584,7 @@ FUZZ_OBJS = FUZZ_PROGRAMS = GIT_OBJS = LIB_OBJS = +NONGIT_PROGRAM_OBJS = OBJECTS = PROGRAM_OBJS = PROGRAMS =
@@ -691,10 +692,17 @@ PROGRAM_OBJS += shell.o .PHONY: program-objs program-objs: $(PROGRAM_OBJS) +ifdef CONTRIB_SCALAR +NONGIT_PROGRAM_OBJS += scalar.o +endif +.PHONY: nongit-program-objs +nongit-program-objs: $(NONGIT_PROGRAM_OBJS) + # Binary suffix, set to .exe for Windows builds X = PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) +PROGRAMS += $(patsubst %.o,%$X,$(NONGIT_PROGRAM_OBJS)) TEST_BUILTINS_OBJS += test-advise.o TEST_BUILTINS_OBJS += test-bitmap.o
@@ -800,6 +808,9 @@ BINDIR_PROGRAMS_NEED_X += git-receive-pack BINDIR_PROGRAMS_NEED_X += git-shell BINDIR_PROGRAMS_NEED_X += git-upload-archive BINDIR_PROGRAMS_NEED_X += git-upload-pack +ifdef CONTRIB_SCALAR +BINDIR_PROGRAMS_NEED_X += scalar +endif BINDIR_PROGRAMS_NO_X += git-cvsserver
@@ -1247,6 +1258,10 @@ else ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X) ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X) ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X) + +ifdef CONTRIB_SCALAR +ALL_COMMANDS_TO_INSTALL += scalar$(X) +endif endif ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS)
@@ -2459,6 +2474,7 @@ git-objs: $(GIT_OBJS) OBJECTS += $(GIT_OBJS) OBJECTS += $(PROGRAM_OBJS) +OBJECTS += $(NONGIT_PROGRAM_OBJS) OBJECTS += $(TEST_OBJS) OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS)
@@ -2582,6 +2598,9 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null endif +ifdef CONTRIB_SCALAR +# TODO: Implicit rule for git-scalar here +endif git-%$X: %.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
@@ -2596,6 +2615,11 @@ git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +ifdef CONTRIB_SCALAR +scalar: scalar.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) +endif + $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY) $(QUIET_LNCP)$(RM) $@ && \ ln $< $@ 2>/dev/null || \
@@ -2800,6 +2824,7 @@ GIT-BUILD-OPTIONS: FORCE @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+ @echo TEST_SHELL_PATH=\''$(subst ','\'',$(TEST_SHELL_PATH_SQ))'\' >>$@+ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+ + @echo CONTRIB_SCALAR=\''$(subst ','\'',$(subst ','\'',$(CONTRIB_SCALAR)))'\' >>$@+ @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+ @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+
@@ -2881,6 +2906,8 @@ bin-wrappers/%: wrap-for-bin.sh $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@@BUILD_DIR@@|$(shell pwd)|' \ -e 's|@@PROG@@|$(patsubst test-%,t/helper/test-%$(X),$(@F))$(patsubst git%,$(X),$(filter $(@F),$(BINDIR_PROGRAMS_NEED_X)))|' < $< > $@ && \ + sed -e 's|scalarscalar|scalar|' <$@ >$@+ && \ + mv $@+ $@ && \ chmod +x $@
Hi Ævar,
Quoted 27 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Johannes Schindelin via GitGitGadget
Changes since v2:
* Adjusted the description of the list command in the manual page , as suggested by Bagas.
* Addressed two style nits incmd_run().
* The documentation of git reconfigure -a was improved.Quoted 6 lines
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with ScalarIn the summary I had on
v1->v2points 1-3 are forv2->v3, respectively, outstanding, addressed, outstanding:https://lore.kernel.org/git/[email protected]/In addition the discussion ending here:
https://lore.kernel.org/git/[email protected]/
Quoted 7 lines
Quoted 4 lines
For that point: I think it's fair enough not to properly handle the cleanup case in "scalar clone", but perhaps add a note in the commit message that unlike "git clone" this is known not to clean after itself properly on ctrl+c?
Seeing [1] about the planned re-roll I have the above a shot a few days ago, see the original discussion at [2] (indirectly linked above).
There is a good reason why I did not engage in that tangent about deviating from the established contrib/*/Makefile paradigm: I find it particularly unrelated to what this here patch series is trying to accomplish, and I cannot bring myself to be interested in the proposed build system changes, either, because I do not see any benefit in the changes, only downsides.
I find the distraction unnecessary.
Besides, the way I designed it, the code in contrib/scalar/ intrudes as little as possible on the core Git build system. The impact on the top-level Makefile is quite minimal, which is just the way I firmly believe it should be.
In short: I do not want those intrusive changes to the top-level Makefile, not in this patch series, and not as a follow-up, either.
We have much bigger fries to fry: namely, how to migrate the improvements for large-scale operations from Scalar to core Git, so that all Git users can benefit. Granted, it will take a lot effort, and it would be easier to move around Makefile rules instead. But ultimately, the benefit of allowing users to handle larger repositories with ease will be worth that effort.
Ciao,
Johannes
Re: [PATCH v2 01/15] scalar: create a rudimentary executable
In reply to Johannes Schindelin via GitGitGadgetQuoted 5 lines · Johannes Schindelin via GitGitGadget
Makefile +0
-0
Makefile endif · line 2456
Quoted 10 lines
Makefile +4
-0@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
Aside from anything else we may disagree with about the build system integration, this breaks the "objects" target. You're adding things to it, but it won't be reflected by those changes.
I have this fixup for it on top locally, the reference to 852ec00310 is to a commit in your repo:
-- >8 --Makefile: fix scalar "make objects" regression
In 852ec00310 (scalar: create a rudimentary executable, 2021-04-10) the contrib/scalar/scalar.o file was added to OBJECTS, but was added below the "objects" target.
That target was added in 029bac01a8 (Makefile: add {program,xdiff,test,git,fuzz}-objs & objects targets, 2021-02-23) along with others (git-objs, test-objs) to serve as ad-hoc targets for e.g. compiling during interactive the targets need to come after we fully declare the variable.
Before this change we'd still do /some/ scalar things on "make objects", namely create the contrib/scalar/.depend/ directory (see dep_dirs in the context), we just wouldn't create the object itself.
--- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
Makefile +2 -3
@@ -2464,12 +2464,11 @@ OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif -.PHONY: objects -objects: $(OBJECTS) - SCALAR_SOURCES := contrib/scalar/scalar.c SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) OBJECTS += $(SCALAR_OBJECTS) +.PHONY: objects +objects: $(OBJECTS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
Signature · 1 line
2.33.0.1013.ge8323766266
Quoted 39 lines · Johannes Schindelin
Hi Ævar,
Quoted 27 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Johannes Schindelin via GitGitGadget
Changes since v2:
* Adjusted the description of the list command in the manual page , as suggested by Bagas.
* Addressed two style nits incmd_run().
* The documentation of git reconfigure -a was improved.Quoted 6 lines
Changes since v1:
* A couple typos were fixed
* The code parsing the output of ls-remote was made more readable
* The indentation used in scalar.txt now consistently uses tabs
* We no longer hard-code core.bare = false when registering with ScalarIn the summary I had on
v1->v2points 1-3 are forv2->v3, respectively, outstanding, addressed, outstanding:https://lore.kernel.org/git/[email protected]/In addition the discussion ending here:
https://lore.kernel.org/git/[email protected]/Quoted 7 lines
Quoted 4 lines
For that point: I think it's fair enough not to properly handle the cleanup case in "scalar clone", but perhaps add a note in the commit message that unlike "git clone" this is known not to clean after itself properly on ctrl+c?
Seeing [1] about the planned re-roll I have the above a shot a few days ago, see the original discussion at [2] (indirectly linked above).
Quoted 8 lines
There is a good reason why I did not engage in that tangent about deviating from the established
contrib/*/Makefileparadigm: I find it particularly unrelated to what this here patch series is trying to accomplish, and I cannot bring myself to be interested in the proposed build system changes, either, because I do not see any benefit in the changes, only downsides.I find the distraction unnecessary.
Perhaps I'm reading too much between the lines here, so forgive any undue knee-jerk reaction.
But aside from any technical disagreement we may have I find this way of handling reviews quite disrespectful of other people's time.
Sure, maybe I'm wrong, and maybe you either don't see any value in the proposed changes or maybe they're just bad suggestions.
I still took the time to review and comment on a series you're submitting. I think the least you can do is to include some comment in a re-roll like:
Skimmed Ævar's proposal about an alternate build system implementation, sorry, I just don't think it's worth it, going to not change anything there.
Which would be fair enough, and would leave the ball in my court in terms of either dropping it, submitting any patch on top etc.
As opposed to just ignoring that whole thread, leaving both me wondering if it's even been seen (and sending a few reminders like the linked upthread), as well as others trying to track the state of the series.
Quoted 7 lines
Besides, the way I designed it, the code in
contrib/scalar/intrudes as little as possible on the core Git build system. The impact on the top-levelMakefileis quite minimal, which is just the way I firmly believe it should be.In short: I do not want those intrusive changes to the top-level
Makefile, not in this patch series, and not as a follow-up, either.
Communication grievances aside:
* Saying that we have an "established contrib/*/Makefile paradigm" doesn't really follow in this case. Most of that isn't C code, and the bits that are C code are not using libgit.a.
And as I've argued elsewhere I think that whole pattern was a mistake in the first place, it makes inter-Makefile dependencies a pain to manage, has resulted in bitrot of things like git-subtree.sh and mw-to-git, all because we conflate whether we want to build/test things with what we'd like in a default installation.
* Because of that any number of targets / workflows in the Makefile aren't going to work by default, e.g. try checking it out and doing "make TAGS".
That specific one happens to because we exclude contrib explicitly, that could be fixed, but there's going to be any number of things like that, but current and future ones.
* One target that seems missing (maybe I've somehow missed it) is any support for installing the build command, its docs etc.
* I hacked a bit more on this today and came up with a not-quite-ready change that both for the Makefile and Documentation/Makefile will build scalar.c like any other top-level command, and we'll always get a bin-wrapper/scalar, we'll only do something different at "install" time.
This means that just like the "all:: $(FUZZ_OBJS)" we'll always build a scalar.o, so that'll prevent others from e.g. breaking a library function you rely on (which'll only be annoying caught in CI, or integration or whatever).
* I think it's fair to say that whatever one thinks of my argument, your [1] leaves the reader hanging about *why* you went for this "make -C contrib/scalar/Makefile".
As summarized there you tried to have it completely separate, but failed.
So now there's a bit of integration in the top-level Makefile already. But why try in the first place? Just slavish conformance to existing "contrib/" convention?
The dependency back on assets in subdirs there is deep, so surely it's not to build this independently somehow.
Quoted 6 lines
We have much bigger fries to fry: namely, how to migrate the improvements for large-scale operations from Scalar to core Git, so that all Git users can benefit. Granted, it will take a lot effort, and it would be easier to move around
Makefilerules instead. But ultimately, the benefit of allowing users to handle larger repositories with ease will be worth that effort.
It's your implementation that requires mostly moving around / copying Makefile rules, if you piggy-back on the existing Makefile rules you'll get most of the behavior you've duplicated for free without any moving or copying.
But in any case, re the last bullet point above and your "[...]and not as a follow-up, either" comments it's not clear whether you're saying that you don't have time to work on this, or that you wouldn't want it at all in any shape or form, even if someone else did it.
Which is not to say that I'm promising to do so even if that's the case, I do think the onus is on the person proposing the change, and to take productive feedback about things that are introducing unnecessary complexity, and won't saddle others with undue technical debt going forward.
Train station analogy, was Re: [PATCH v3 00/15] [RFC] Upstreaming the Scalar command
Hi Ævar,
Quoted 30 lines · Ævar Arnfjörð Bjarmason
Quoted 28 lines · Johannes Schindelin
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 8 lines · Ævar Arnfjörð Bjarmason
In the summary I had on
v1->v2points 1-3 are forv2->v3, respectively, outstanding, addressed, outstanding:https://lore.kernel.org/git/[email protected]/In addition the discussion ending here:
https://lore.kernel.org/git/[email protected]/Quoted 8 lines
Quoted 4 lines
For that point: I think it's fair enough not to properly handle the cleanup case in "scalar clone", but perhaps add a note in the commit message that unlike "git clone" this is known not to clean after itself properly on ctrl+c?
Seeing [1] about the planned re-roll I have the above a shot a few days ago, see the original discussion at [2] (indirectly linked above).
There is a good reason why I did not engage in that tangent about deviating from the established
contrib/*/Makefileparadigm: I find it particularly unrelated to what this here patch series is trying to accomplish, and I cannot bring myself to be interested in the proposed build system changes, either, because I do not see any benefit in the changes, only downsides.
Quoted 4 lines
Quoted 1 line
I find the distraction unnecessary.
Perhaps I'm reading too much between the lines here, so forgive any undue knee-jerk reaction.
Okay, let's try an analogy.
Imagine that a person is asking for directions to the train station. And the other person is replying by asking "did you know that this train station was built in 1878? It is actually quite interesting a story... [and then goes on to describe the history and what excites them about it]". Now, the first person tries again to ask for directions, again does not get an answer to that question, and is slowly starting to look at their watch. The second person, being completely oblivious to all of this, goes on with their wonderful story about the train station and its cultural heritage. So the first person walks a bit further to ask a third person, but the second person is not done yet and says "but you haven't heard me out! That's disrespectful!".
Just imagine for a minute how you would feel if you were the first person.
And that is how I feel asking for reviews about the Scalar patch series and then being forcefully dragged into that tangent about the build process.
I find the well-established paradigm to keep contrib/'s build procedures as confined to their own directory as possible the most reasonable way to handle the build by virtue of _not_ polluting the top-level Makefile unnecessarily. All of your objections strike me simply as personal viewpoints, not as technical arguments, and they fail to address this "pollution of the top-level Makefile" problem. I therefore strongly disagree with your suggestion that the build system should be changed, I would even argue that your suggestion should been dismissed on purely technical grounds, and I wish you hadn't forced me to say this as forcefully.
And even if I looked more favorably on your suggestion to change the build procedure, I find this distraction about the build as little constructive as the explanations about the train station's history above. Those suggestions do succeed in derailing the conversation about how Git could scale better, how Scalar _does_ teach Git how to scale better, and about how to teach Git itself more and more of Scalar's tricks.
If you have ideas how to teach, say, git clone to perform a couple of Scalar's tricks, by all means, let's hear them, or even better, let's see those patches. If you want to change the build system, still, I cannot stop you from sending patches to that end to the Git mailing list, but please expect me to be uninterested in them in any way, and to prefer to spend my efforts to improve Git elsewhere. If you have other ideas how to improve on Scalar in a user-perceptible way, however, I am all ears again.
I hope this clarifies it, without the need to read between the lines, Johannes
PATCH v4 1/15 scalar: create a rudimentary executable
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The idea of Scalar (https://github.com/microsoft/scalar), and before that, of VFS for Git, has always been to prove that Git _can_ scale, and to upstream whatever strategies have been demonstrated to help.
With this patch, we start the journey from that C# project to move what is left to Git's own contrib/ directory, reimplementing it in pure C, with the intention to facilitate integrating the functionality into core Git all while maintaining backwards-compatibility for existing Scalar users (which will be much easier when both live in the same worktree). It has always been the plan to contribute all of the proven strategies back to core Git.
For example, while the virtual filesystem provided by VFS for Git helped the team developing the Windows operating system to move onto Git, while trying to upstream it we realized that it cannot be done: getting the virtual filesystem to work (which we only managed to implement fully on Windows, but not on, say, macOS or Linux), and the required server-side support for the GVFS protocol, made this not quite feasible.
The Scalar project learned from that and tackled the problem with different tactics: instead of pretending to Git that the working directory is fully populated, it _specifically_ teaches Git about partial clone (which is based on VFS for Git's cache server), about sparse checkout (which VFS for Git tried to do transparently, in the file system layer), and regularly runs maintenance tasks to keep the repository in a healthy state.
With partial clone, sparse checkout and git maintenance having been upstreamed, there is little left that scalar.exe does which git.exe cannot do. One such thing is that scalar clone <url> will automatically set up a partial, sparse clone, and configure known-helpful settings from the start.
So let's bring this convenience into Git's tree.
The idea here is that you can (optionally) build Scalar via
make -C contrib/scalar/Makefile
This will build the scalar executable and put it into the contrib/scalar/ subdirectory.
The slightly awkward addition of the contrib/scalar/* bits to the top-level Makefile are actually really required: we want to link to libgit.a, which means that we will need to use the very same CFLAGS and LDFLAGS as the rest of Git.
An early development version of this patch tried to replicate all the conditional code in contrib/scalar/Makefile (e.g. NO_POLL) just like contrib/svn-fe/Makefile used to do before it was retired. It turned out to be quite the whack-a-mole game: the SHA-1-related flags, the flags enabling/disabling compat/poll/, compat/regex/, compat/win32mmap.c & friends depending on the current platform... To put it mildly: it was a major mess.
Instead, this patch makes minimal changes to the top-level Makefile so that the bits in contrib/scalar/ can be compiled and linked, and adds a contrib/scalar/Makefile that uses the top-level Makefile in a most minimal way to do the actual compiling.
Note: With this commit, we only establish the infrastructure, no Scalar functionality is implemented yet; We will do that incrementally over the next few commits.
--- Makefile | 8 ++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +8 -0
@@ -2447,6 +2447,10 @@ endif .PHONY: objects objects: $(OBJECTS) +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2586,6 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) +contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
contrib/scalar/.gitignore new file +2 -0
@@ -0,0 +1,2 @@ +/*.exe +/scalar
contrib/scalar/Makefile new file +34 -0
@@ -0,0 +1,34 @@ +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring s,$(MAKEFLAGS)),s) +ifndef V + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir +else + export V +endif +endif + +all: + +include ../../config.mak.uname +-include ../../config.mak.autogen +-include ../../config.mak + +TARGETS = scalar$(X) scalar.o +GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a + +all: scalar$X + +$(GITLIBS): + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) + +$(TARGETS): $(GITLIBS) scalar.c + $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) + +clean: + $(RM) $(TARGETS) + +.PHONY: all clean FORCE
contrib/scalar/scalar.c new file +36 -0
@@ -0,0 +1,36 @@ +/* +* The Scalar command-line interface. +*/ + +#include "cache.h" +#include "gettext.h" +#include "parse-options.h" + +static struct { +const char *name; +int (*fn)(int ,const char **); +} builtins[] = { + {NULL ,NULL }, +}; + +int cmd_main (int argc,const char **argv) +{ +struct strbuf scalar_usage =STRBUF_INIT ; +int i; + +if (argc >1 ) { + argv++; + argc--; + +for (i =0 ; builtins[i].name; i++) +if (!strcmp (builtins[i].name, argv[0 ])) +return !!builtins[i].fn (argc, argv); + } + +strbuf_addstr (&scalar_usage, +N_ ("scalar <command> [<options>]\n\nCommands:\n" )); +for (i =0 ; builtins[i].name; i++) +strbuf_addf (&scalar_usage,"\t%s\n" , builtins[i].name); + +usage (scalar_usage.buf); +}
Signature · 1 line
gitgitgadget
PATCH v4 2/15 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This commit establishes the infrastructure to build the manual page for the scalar command.
--- contrib/scalar/.gitignore | 3 +++ contrib/scalar/Makefile | 14 +++++++++++++- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 contrib/scalar/scalar.txt
contrib/scalar/.gitignore +3 -0
@@ -1,2 +1,5 @@ +/*.xml +/*.1 +/*.html /*.exe /scalar
contrib/scalar/Makefile +13 -1
@@ -6,6 +6,7 @@ ifndef V QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir + QUIET = @ else export V endif
@@ -30,5 +31,16 @@ $(TARGETS): $(GITLIBS) scalar.c clean: $(RM) $(TARGETS) + $(RM) scalar.1 scalar.html scalar.xml -.PHONY: all clean FORCE +docs: scalar.html scalar.1 + +scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel + +scalar.html scalar.1: scalar.txt + $(QUIET_SUBDIR0)../../Documentation$(QUIET_SUBDIR1) \ + MAN_TXT=../contrib/scalar/scalar.txt \ + ../contrib/scalar/$@ + $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . + +.PHONY: all clean docs FORCE
contrib/scalar/scalar.txt new file +38 -0
@@ -0,0 +1,38 @@ +scalar(1) +========= + +NAME +---- +scalar - an opinionated repository management tool + +SYNOPSIS +-------- +[verse] +scalar <command> [<options>] + +DESCRIPTION +----------- + +Scalar is an opinionated repository management tool. By creating new +repositories or registering existing repositories with Scalar, your Git +experience will speed up. Scalar sets advanced Git config settings, +maintains your repositories in the background, and helps reduce data sent +across the network. + +An important Scalar concept is the enlistment: this is the top-level directory +of the project. It usually contains the subdirectory `src/` which is a Git +worktree. This encourages the separation between tracked files (inside `src/`) +and untracked files, such as build artifacts (outside `src/`). When registering +an existing Git worktree with Scalar whose name is not `src`, the enlistment +will be identical to the worktree. + +The `scalar` command implements various subcommands, and different options +depending on the subcommand. + +SEE ALSO +-------- +linkgit:git-maintenance[1]. + +Scalar +--- +Associated with the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
PATCH v4 3/15 scalar: create test infrastructure
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
To test the Scalar command, create a test script in contrib/scalar/t that is executed as make -C contrib/scalar test. Since Scalar has no meaningful capabilities yet, the only test is rather simple. We will add more tests in subsequent commits that introduce corresponding, new functionality.
Note: this test script is intended to test scalar only lightly, even after all of the functionality is implemented.
A more comprehensive functional (or: integration) test suite can be found at https://github.com/microsoft/scalar; It is used in the workflow
https://github.com/microsoft/git/blob/HEAD/.github/workflows/scalar-functional-tests.yml
in Microsoft's Git fork. This test suite performs end-to-end tests with a real remote repository, and is run as part of the regular CI builds. Since those tests require some functionality supported only by Microsoft's Git fork ("GVFS protocol"), there is no intention to port that fuller test suite to contrib/scalar/.
--- contrib/scalar/Makefile | 17 +++++-- contrib/scalar/t/Makefile | 78 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 17 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 contrib/scalar/t/Makefile create mode 100755 contrib/scalar/t/t9099-scalar.sh
contrib/scalar/Makefile +14 -3
@@ -3,6 +3,7 @@ QUIET_SUBDIR1 = ifneq ($(findstring s,$(MAKEFLAGS)),s) ifndef V + QUIET_GEN = @echo ' ' GEN $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -21,7 +22,7 @@ include ../../config.mak.uname TARGETS = scalar$(X) scalar.o GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a -all: scalar$X +all: scalar$X ../../bin-wrappers/scalar $(GITLIBS): $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ -30,9 +31,19 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar $(RM) scalar.1 scalar.html scalar.xml +../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile + @mkdir -p ../../bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell cd ../.. && pwd)|' \ + -e 's|@@PROG@@|contrib/scalar/scalar$(X)|' < $< > $@ && \ + chmod +x $@ + +test: all + $(MAKE) -C t + docs: scalar.html scalar.1 scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
@@ -43,4 +54,4 @@ scalar.html scalar.1: scalar.txt ../contrib/scalar/$@ $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ . -.PHONY: all clean docs FORCE +.PHONY: all clean docs test FORCE
contrib/scalar/t/Makefile new file +78 -0
78 lines not on this page · raw message
contrib/scalar/t/t9099-scalar.sh new file +17 -0
@@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='test the `scalar` command' + +TEST_DIRECTORY=$PWD/../../../t +export TEST_DIRECTORY + +# Make it work with --no-bin-wrappers +PATH=$PWD/..:$PATH + +. ../../../t/test-lib.sh + +test_expect_success 'scalar shows a usage' ' + test_expect_code 129 scalar -h +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v4 4/15 scalar: 'register' sets recommended config and starts maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Let's start implementing the register command. With this commit, recommended settings are configured upon scalar register, and Git's background maintenance is started.
The recommended config settings may very well change in the future. For example, once the built-in FSMonitor is available, we will want to enable it upon scalar register. For that reason, we explicitly support running scalar register in an already-registered enlistment.
--- contrib/scalar/scalar.c | 255 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 272 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +255 -0
@@ -5,11 +5,266 @@#include "cache.h" #include "gettext.h" #include "parse-options.h" +#include "config.h" +#include "run-command.h" + +/* +* Remove the deepest subdirectory in the provided path string. Path must not +* include a trailing path separator. Returns 1 if parent directory found, +* otherwise 0. +*/ +static int strbuf_parent_directory (struct strbuf *buf) +{ +size_t len = buf->len; +size_t offset =offset_1st_component (buf->buf); +char *path_sep =find_last_dir_sep (buf->buf + offset); +strbuf_setlen (buf, path_sep ? path_sep - buf->buf : offset); + +return buf->len < len; +} + +static void setup_enlistment_directory (int argc,const char **argv, +const char *const *usagestr, +const struct option *options, +struct strbuf *enlistment_root) +{ +struct strbuf path =STRBUF_INIT ; +char *root; +int enlistment_found =0 ; + +if (startup_info->have_repository) +BUG ("gitdir already set up?!?" ); + +if (argc >1 ) +usage_with_options (usagestr, options); + +/* find the worktree, determine its corresponding root */ +if (argc ==1 ) +strbuf_add_absolute_path (&path, argv[0 ]); +else if (strbuf_getcwd (&path) <0 ) +die (_ ("need a working directory" )); + +strbuf_trim_trailing_dir_sep (&path); +do { +const size_t len = path.len; + +/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) { +strbuf_strip_suffix (&path,"/.git" ); + +if (enlistment_root) +strbuf_add (enlistment_root, path.buf, len); + + enlistment_found =1 ; +break ; + } + +/* reset to original path */ +strbuf_setlen (&path, len); + +/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) { +strbuf_setlen (&path, len); + +if (enlistment_root) { +/* +* If the worktree's directory's name is `src`, the enlistment is the +* parent directory, otherwise it is identical to the worktree. +*/ + root =strip_path_suffix (path.buf,"src" ); +strbuf_addstr (enlistment_root, root ? root : path.buf); +free (root); + } + + enlistment_found =1 ; +break ; + } + +strbuf_setlen (&path, len); + }while (strbuf_parent_directory (&path)); + +if (!enlistment_found) +die (_ ("could not find enlistment root" )); + +if (chdir (path.buf) <0 ) +die_errno (_ ("could not switch to '%s'" ), path.buf); + +strbuf_release (&path); +setup_git_directory (); +} + +static int run_git (const char *arg, ...) +{ +struct strvec argv =STRVEC_INIT ; +va_list args; +const char *p; +int res; + +va_start (args, arg); +strvec_push (&argv, arg); + while ((p = va_arg(args, const char *))) +strvec_push (&argv, p); +va_end (args); + + res =run_command_v_opt (argv.v,RUN_GIT_CMD ); + +strvec_clear (&argv); +return res; +} + +static int set_recommended_config (void ) +{ +struct { +const char *key; +const char *value; + } config[] = { + {"am.keepCR" ,"true" }, + {"core.FSCache" ,"true" }, + {"core.multiPackIndex" ,"true" }, + {"core.preloadIndex" ,"true" }, +#ifndef WIN32 + {"core.untrackedCache" ,"true" }, +#else +/* +* Unfortunately, Scalar's Functional Tests demonstrated +* that the untracked cache feature is unreliable on Windows +* (which is a bummer because that platform would benefit the +* most from it). For some reason, freshly created files seem +* not to update the directory's `lastModified` time +* immediately, but the untracked cache would need to rely on +* that. +* +* Therefore, with a sad heart, we disable this very useful +* feature on Windows. +*/ + {"core.untrackedCache" ,"false" }, +#endif + {"core.logAllRefUpdates" ,"true" }, + {"credential.https://dev.azure.com.useHttpPath" ,"true" }, + {"credential.validate" ,"false" },/* GCM4W-only */ + {"gc.auto" ,"0" }, + {"gui.GCWarning" ,"false" }, + {"index.threads" ,"true" }, + {"index.version" ,"4" }, + {"merge.stat" ,"false" }, + {"merge.renames" ,"false" }, + {"pack.useBitmaps" ,"false" }, + {"pack.useSparse" ,"true" }, + {"receive.autoGC" ,"false" }, + {"reset.quiet" ,"true" }, + {"feature.manyFiles" ,"false" }, + {"feature.experimental" ,"false" }, + {"fetch.unpackLimit" ,"1" }, + {"fetch.writeCommitGraph" ,"false" }, +#ifdef WIN32 + {"http.sslBackend" ,"schannel" }, +#endif + {"status.aheadBehind" ,"false" }, + {"commitGraph.generationVersion" ,"1" }, + {"core.autoCRLF" ,"false" }, + {"core.safeCRLF" ,"false" }, + {NULL ,NULL }, + }; +int i; +char *value; + +for (i =0 ; config[i].key; i++) { +if (git_config_get_string (config[i].key, &value)) { +trace2_data_string ("scalar" , the_repository, config[i].key,"created" ); +if (git_config_set_gently (config[i].key, + config[i].value) <0 ) +return error (_ ("could not configure %s=%s" ), + config[i].key, config[i].value); + }else { +trace2_data_string ("scalar" , the_repository, config[i].key,"exists" ); +free (value); + } + } + +/* +* The `log.excludeDecoration` setting is special because it allows +* for multiple values. +*/ +if (git_config_get_string ("log.excludeDecoration" , &value)) { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"created" ); +if (git_config_set_multivar_gently ("log.excludeDecoration" , +"refs/prefetch/*" , +CONFIG_REGEX_NONE ,0 )) +return error (_ ("could not configure " +"log.excludeDecoration" )); + }else { +trace2_data_string ("scalar" , the_repository, +"log.excludeDecoration" ,"exists" ); +free (value); + } + +return 0 ; +} + +static int start_maintenance (void ) +{ +return run_git ("maintenance" ,"start" ,NULL ); +} + +static int add_enlistment (void ) +{ +int res; + +if (!the_repository->worktree) +die (_ ("Scalar enlistments require a worktree" )); + + res =run_git ("config" ,"--global" ,"--get" ,"--fixed-value" , +"scalar.repo" , the_repository->worktree,NULL ); + +/* +* If the setting is already there, then do nothing. +*/ +if (!res) +return 0 ; + +return run_git ("config" ,"--global" ,"--add" , +"scalar.repo" , the_repository->worktree,NULL ); +} + +static int register_dir (void ) +{ +int res =add_enlistment (); + +if (!res) + res =set_recommended_config (); + +if (!res) + res =start_maintenance (); + +return res; +} + +static int cmd_register (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar register [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return register_dir (); +}static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + {"register" , cmd_register }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +17 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar <command> [<options>] +scalar register [<enlistment>] DESCRIPTION -----------
@@ -29,6 +29,22 @@ will be identical to the worktree. The `scalar` command implements various subcommands, and different options depending on the subcommand. +COMMANDS +-------- + +Register +~~~~~~~~ + +register [<enlistment>]:: + Adds the enlistment's repository to the list of registered repositories + and starts background maintenance. If `<enlistment>` is not provided, + then the enlistment associated with the current working directory is + registered. ++ +Note: when this subcommand is called in a worktree that is called `src/`, its +parent directory is considered to be the Scalar enlistment. If the worktree is +_not_ called `src/`, it itself will be considered to be the Scalar enlistment. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v4 5/15 scalar: 'unregister' stops background maintenance
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Just like scalar register starts the scheduled background maintenance, scalar unregister stops it. Note that we use git maintenance start in scalar register, but we do not use git maintenance stop in scalar unregister: this would stop maintenance for _all_ repositories, not just for the one we want to unregister.
The unregister command also removes the corresponding entry from the [scalar] section in the global Git config.
--- contrib/scalar/scalar.c | 50 ++++++++++++++++++++++++++++++++------- contrib/scalar/scalar.txt | 8 +++++++ 2 files changed, 50 insertions(+), 8 deletions(-)
contrib/scalar/scalar.c +42 -8
@@ -204,12 +204,12 @@ static int set_recommended_config(void)return 0 ; } -static int start_maintenance (void ) +static int toggle_maintenance (int enable) { -return run_git ("maintenance" ,"start" ,NULL ); +return run_git ("maintenance" , enable ?"start" :"unregister" ,NULL ); } -static int add_enlistment (void ) +static int add_or_remove_enlistment (int add) {int res;
@@ -220,24 +220,39 @@ static int add_enlistment(void) "scalar.repo", the_repository->worktree, NULL);/* -* If the setting is already there, then do nothing. +* If we want to add and the setting is already there, then do nothing. +* If we want to remove and the setting is not there, then do nothing. */ -if (!res) +if ((add && !res) || (!add && res))return 0 ; -return run_git ("config" ,"--global" ,"--add" , +return run_git ("config" ,"--global" , add ?"--add" :"--unset" , + add ?"--no-fixed-value" :"--fixed-value" ,"scalar.repo" , the_repository->worktree,NULL ); }static int register_dir (void ) { -int res =add_enlistment (); +int res =add_or_remove_enlistment (1 );if (!res) res =set_recommended_config ();if (!res) - res =start_maintenance (); + res =toggle_maintenance (1 ); + +return res; +} + +static int unregister_dir (void ) +{ +int res =0 ; + +if (toggle_maintenance (0 ) <0 ) + res =-1 ; + +if (add_or_remove_enlistment (0 ) <0 ) + res =-1 ;return res; }
@@ -260,11 +275,30 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_unregister (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar unregister [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return unregister_dir (); +} +static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { {"register" , cmd_register }, + {"unregister" , cmd_unregister }, {NULL ,NULL }, };
contrib/scalar/scalar.txt +8 -0
@@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] scalar register [<enlistment>] +scalar unregister [<enlistment>] DESCRIPTION -----------
@@ -45,6 +46,13 @@ Note: when this subcommand is called in a worktree that is called `src/`, its parent directory is considered to be the Scalar enlistment. If the worktree is _not_ called `src/`, it itself will be considered to be the Scalar enlistment. +Unregister +~~~~~~~~~~ + +unregister [<enlistment>]:: + Remove the specified repository from the list of repositories + registered with Scalar and stop the scheduled background maintenance. + SEE ALSO -------- linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget
PATCH v4 6/15 scalar: let 'unregister' handle a deleted enlistment directory gracefully
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
When a user deleted an enlistment manually, let's be generous and _still_ unregister it.
--- contrib/scalar/scalar.c | 46 ++++++++++++++++++++++++++++++++ contrib/scalar/t/t9099-scalar.sh | 15 +++++++++++ 2 files changed, 61 insertions(+)
contrib/scalar/scalar.c +46 -0
@@ -275,6 +275,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int remove_deleted_enlistment (struct strbuf *path) +{ +int res =0 ; +strbuf_realpath_forgiving (path, path->buf,1 ); + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"scalar.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +if (run_git ("config" ,"--global" , +"--unset" ,"--fixed-value" , +"maintenance.repo" , path->buf,NULL ) <0 ) + res =-1 ; + +return res; +} +static int cmd_unregister (int argc,const char **argv) {struct option options[] = {
@@ -288,6 +306,34 @@ static int cmd_unregister(int argc, const char **argv) argc =parse_options (argc, argv,NULL , options, usage,0 ); +/* +* Be forgiving when the enlistment or worktree does not even exist any +* longer; This can be the case if a user deleted the worktree by +* mistake and _still_ wants to unregister the thing. +*/ +if (argc ==1 ) { +struct strbuf src_path =STRBUF_INIT , workdir_path =STRBUF_INIT ; + +strbuf_addf (&src_path,"%s/src/.git" , argv[0 ]); +strbuf_addf (&workdir_path,"%s/.git" , argv[0 ]); +if (!is_directory (src_path.buf) && !is_directory (workdir_path.buf)) { +/* remove possible matching registrations */ +int res =-1 ; + +strbuf_strip_suffix (&src_path,"/.git" ); + res =remove_deleted_enlistment (&src_path) && res; + +strbuf_strip_suffix (&workdir_path,"/.git" ); + res =remove_deleted_enlistment (&workdir_path) && res; + +strbuf_release (&src_path); +strbuf_release (&workdir_path); +return res; + } +strbuf_release (&src_path); +strbuf_release (&workdir_path); + } +setup_enlistment_directory (argc, argv, usage, options,NULL );return unregister_dir ();
contrib/scalar/t/t9099-scalar.sh +15 -0
@@ -14,4 +14,19 @@ test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h ' +test_expect_success 'scalar unregister' ' + git init vanish/src && + scalar register vanish/src && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/vanish/src" scalar.repos && + rm -rf vanish/src/.git && + scalar unregister vanish && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/vanish/src" scalar.repos +' + test_done
Signature · 1 line
gitgitgadget
PATCH v4 7/15 scalar: implement 'scalar list'
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The produced list simply consists of those repositories registered under the multi-valued scalar.repo config setting in the user's Git config.
--- contrib/scalar/scalar.c | 11 +++++++++++ contrib/scalar/scalar.txt | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +11 -0
@@ -257,6 +257,16 @@ static int unregister_dir(void)return res; } +static int cmd_list (int argc,const char **argv) +{ +if (argc !=1 ) +die (_ ("`scalar list` does not take arguments" )); + +if (run_git ("config" ,"--global" ,"--get-all" ,"scalar.repo" ,NULL ) <0 ) +return -1 ; +return 0 ; +} +static int cmd_register (int argc,const char **argv) {struct option options[] = {
@@ -343,6 +353,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, { NULL, NULL},
contrib/scalar/scalar.txt +10 -1
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -28,11 +29,19 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. +depending on the subcommand. With the exception of `list`, all subcommands +expect to be run in an enlistment. COMMANDS -------- +List +~~~~ + +list:: + List enlistments that are currently registered by Scalar. This + subcommand does not need to be run inside an enlistment. + Register ~~~~~~~~
Signature · 1 line
gitgitgadget
PATCH v4 8/15 scalar: implement the `clone` subcommand
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This implements Scalar's opinionated clone command: it tries to use a partial clone and sets up a sparse checkout by default. In contrast to git clone, scalar clone sets up the worktree in the src/ subdirectory, to encourage a separation between the source files and the build output (which helps Git tremendously because it avoids untracked files that have to be specifically ignored when refreshing the index).
Also, it registers the repository for regular, scheduled maintenance, and configures a flurry of configuration settings based on the experience and experiments of the Microsoft Windows and the Microsoft Office development teams.
Note: since the scalar clone command is by far the most commonly called scalar subcommand, we document it at the top of the manual page.
--- contrib/scalar/scalar.c | 201 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 31 ++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 261 insertions(+), 3 deletions(-)
contrib/scalar/scalar.c +201 -0
@@ -7,6 +7,7 @@#include "parse-options.h" #include "config.h" #include "run-command.h" +#include "refs.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -257,6 +258,205 @@ static int unregister_dir(void)return res; } +/* printf-style interface, expects `<key>=<value>` argument */ +static int set_config (const char *fmt, ...) +{ +struct strbuf buf =STRBUF_INIT ; +char *value; +int res; +va_list args; + +va_start (args, fmt); +strbuf_vaddf (&buf, fmt, args); +va_end (args); + + value =strchr (buf.buf,'=' ); +if (value) + *(value++) ='\0' ; + res =git_config_set_gently (buf.buf, value); +strbuf_release (&buf); + +return res; +} + +static char *remote_default_branch (const char *url) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +struct strbuf out =STRBUF_INIT ; + + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"ls-remote" ,"--symref" , url,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +const char *line = out.buf; + +while (*line) { +const char *eol =strchrnul (line,'\n' ), *p; +size_t len = eol - line; +char *branch; + +if (!skip_prefix (line,"ref: " , &p) || + !strip_suffix_mem (line, &len,"\tHEAD" )) { + line = eol + (*eol =='\n' ); +continue ; + } + + eol = line + len; +if (skip_prefix (p,"refs/heads/" , &p)) { + branch =xstrndup (p, eol - p); +strbuf_release (&out); +return branch; + } + +error (_ ("remote HEAD is not a branch: '%.*s'" ), + (int )(eol - p), p); +strbuf_release (&out); +return NULL ; + } + } +warning (_ ("failed to get default branch name from remote; " +"using local default" )); +strbuf_reset (&out); + +child_process_init (&cp); + cp.git_cmd =1 ; +strvec_pushl (&cp.args,"symbolic-ref" ,"--short" ,"HEAD" ,NULL ); +if (!pipe_command (&cp,NULL ,0 , &out,0 ,NULL ,0 )) { +strbuf_trim (&out); +return strbuf_detach (&out,NULL ); + } + +strbuf_release (&out); +error (_ ("failed to get default branch name" )); +return NULL ; +} + +static int cmd_clone (int argc,const char **argv) +{ +const char *branch =NULL ; +int full_clone =0 ; +struct option clone_options[] = { +OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ), +N_ ("branch to checkout after clone" )), +OPT_BOOL (0 ,"full-clone" , &full_clone, +N_ ("when cloning, create full working directory" )), +OPT_END (), + }; +const char *const clone_usage[] = { +N_ ("scalar clone [<options>] [--] <repo> [<dir>]" ), +NULL + }; +const char *url; +char *enlistment =NULL , *dir =NULL ; +struct strbuf buf =STRBUF_INIT ; +int res; + + argc =parse_options (argc, argv,NULL , clone_options, clone_usage,0 ); + +if (argc ==2 ) { + url = argv[0 ]; + enlistment =xstrdup (argv[1 ]); + }else if (argc ==1 ) { + url = argv[0 ]; + +strbuf_addstr (&buf, url); +/* Strip trailing slashes, if any */ +while (buf.len >0 &&is_dir_sep (buf.buf[buf.len -1 ])) +strbuf_setlen (&buf, buf.len -1 ); +/* Strip suffix `.git`, if any */ +strbuf_strip_suffix (&buf,".git" ); + + enlistment =find_last_dir_sep (buf.buf); +if (!enlistment) { +die (_ ("cannot deduce worktree name from '%s'" ), url); + } + enlistment =xstrdup (enlistment +1 ); + }else { +usage_msg_opt (_ ("You must specify a repository to clone." ), + clone_usage, clone_options); + } + +if (is_directory (enlistment)) +die (_ ("directory '%s' exists already" ), enlistment); + + dir =xstrfmt ("%s/src" , enlistment); + +strbuf_reset (&buf); +if (branch) +strbuf_addf (&buf,"init.defaultBranch=%s" , branch); +else { +char *b =repo_default_branch_name (the_repository,1 ); +strbuf_addf (&buf,"init.defaultBranch=%s" , b); +free (b); + } + +if ((res =run_git ("-c" , buf.buf,"init" ,"--" , dir,NULL ))) + goto cleanup; + +if (chdir (dir) <0 ) { + res =error_errno (_ ("could not switch to '%s'" ), dir); + goto cleanup; + } + +setup_git_directory (); + +/* common-main already logs `argv` */ +trace2_def_repo (the_repository); + +if (!branch && !(branch =remote_default_branch (url))) { + res =error (_ ("failed to get default branch for '%s'" ), url); + goto cleanup; + } + +if (set_config ("remote.origin.url=%s" , url) || +set_config ("remote.origin.fetch=" +"+refs/heads/*:refs/remotes/origin/*" ) || +set_config ("remote.origin.promisor=true" ) || +set_config ("remote.origin.partialCloneFilter=blob:none" )) { + res =error (_ ("could not configure remote in '%s'" ), dir); + goto cleanup; + } + +if (!full_clone && + (res =run_git ("sparse-checkout" ,"init" ,"--cone" ,NULL ))) + goto cleanup; + +if (set_recommended_config ()) +return error (_ ("could not configure '%s'" ), dir); + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) { +warning (_ ("partial clone failed; attempting full clone" )); + +if (set_config ("remote.origin.promisor" ) || +set_config ("remote.origin.partialCloneFilter" )) { + res =error (_ ("could not configure for full clone" )); + goto cleanup; + } + +if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) + goto cleanup; + } + +if ((res =set_config ("branch.%s.remote=origin" , branch))) + goto cleanup; +if ((res =set_config ("branch.%s.merge=refs/heads/%s" , + branch, branch))) + goto cleanup; + +strbuf_reset (&buf); +strbuf_addf (&buf,"origin/%s" , branch); + res =run_git ("checkout" ,"-f" ,"-t" , buf.buf,NULL ); +if (res) + goto cleanup; + + res =register_dir (); + +cleanup: +free (enlistment); +free (dir); +strbuf_release (&buf); +return res; +} +static int cmd_list (int argc,const char **argv) {if (argc !=1 )
@@ -353,6 +553,7 @@ static struct {const char *name;int (*fn)(int ,const char **); } builtins[] = { + { "clone", cmd_clone }, { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister },
contrib/scalar/scalar.txt +28 -3
@@ -8,6 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] +scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -29,12 +30,36 @@ an existing Git worktree with Scalar whose name is not `src`, the enlistment will be identical to the worktree. The `scalar` command implements various subcommands, and different options -depending on the subcommand. With the exception of `list`, all subcommands -expect to be run in an enlistment. +depending on the subcommand. With the exception of `clone` and `list`, all +subcommands expect to be run in an enlistment. COMMANDS -------- +Clone +~~~~~ + +clone [<options>] <url> [<enlistment>]:: + Clones the specified repository, similar to linkgit:git-clone[1]. By + default, only commit and tree objects are cloned. Once finished, the + worktree is located at `<enlistment>/src`. ++ +The sparse-checkout feature is enabled (except when run with `--full-clone`) +and the only files present are those in the top-level directory. Use +`git sparse-checkout set` to expand the set of directories you want to see, +or `git sparse-checkout disable` to expand to all files (see +linkgit:git-sparse-checkout[1] for more details). You can explore the +subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. + +-b <name>:: +--branch <name>:: + Instead of checking out the branch pointed to by the cloned + repository's HEAD, check out the `<name>` branch instead. + +--[no-]full-clone:: + A sparse-checkout is initialized by default. This behavior can be + turned off via `--full-clone`. + List ~~~~
@@ -64,7 +89,7 @@ unregister [<enlistment>]:: SEE ALSO -------- -linkgit:git-maintenance[1]. +linkgit:git-clone[1], linkgit:git-maintenance[1]. Scalar ---
contrib/scalar/t/t9099-scalar.sh +32 -0
@@ -10,6 +10,9 @@ PATH=$PWD/..:$PATH . ../../../t/test-lib.sh +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt" +export GIT_TEST_MAINT_SCHEDULER + test_expect_success 'scalar shows a usage' ' test_expect_code 129 scalar -h '
@@ -29,4 +32,33 @@ test_expect_success 'scalar unregister' ' ! grep -F "$(pwd)/vanish/src" scalar.repos ' +test_expect_success 'set up repository to clone' ' + test_commit first && + test_commit second && + test_commit third && + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + git config uploadPack.allowFilter true && + git config uploadPack.allowAnySHA1InWant true +' + +test_expect_success 'scalar clone' ' + second=$(git rev-parse --verify second:second.t) && + scalar clone "file://$(pwd)" cloned && + ( + cd cloned/src && + + git config --get --global --fixed-value maintenance.repo \ + "$(pwd)" && + + test_path_is_missing 1/2 && + test_must_fail git rev-list --missing=print $second && + git rev-list $second && + git cat-file blob $second >actual && + echo "second" >expect && + test_cmp expect actual + ) +' + test_done
Signature · 1 line
gitgitgadget
PATCH v4 9/15 scalar: teach 'clone' to support the --single-branch option
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like git clone, the scalar clone command now also offers to restrict the clone to a single branch.
--- contrib/scalar/scalar.c | 9 +++++++-- contrib/scalar/scalar.txt | 12 +++++++++++- contrib/scalar/t/t9099-scalar.sh | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-)
contrib/scalar/scalar.c +7 -2
@@ -333,12 +333,15 @@ static char *remote_default_branch(const char *url)static int cmd_clone (int argc,const char **argv) {const char *branch =NULL ; -int full_clone =0 ; +int full_clone =0 , single_branch =0 ;struct option clone_options[] = {OPT_STRING ('b' ,"branch" , &branch,N_ ("<branch>" ),N_ ("branch to checkout after clone" )),OPT_BOOL (0 ,"full-clone" , &full_clone,N_ ("when cloning, create full working directory" )), +OPT_BOOL (0 ,"single-branch" , &single_branch, +N_ ("only download metadata for the branch that will " +"be checked out" )),OPT_END (), };const char *const clone_usage[] = {
@@ -409,7 +412,9 @@ static int cmd_clone(int argc, const char **argv)if (set_config ("remote.origin.url=%s" , url) ||set_config ("remote.origin.fetch=" -"+refs/heads/*:refs/remotes/origin/*" ) || +"+refs/heads/%s:refs/remotes/origin/%s" , + single_branch ? branch :"*" , + single_branch ? branch :"*" ) ||set_config ("remote.origin.promisor=true" ) ||set_config ("remote.origin.partialCloneFilter=blob:none" )) { res =error (_ ("could not configure remote in '%s'" ), dir);
contrib/scalar/scalar.txt +11 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool SYNOPSIS -------- [verse] -scalar clone [--branch <main-branch>] [--full-clone] <url> [<enlistment>] +scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>]
@@ -56,6 +56,16 @@ subdirectories outside your sparse-checkout by using `git ls-tree HEAD`. Instead of checking out the branch pointed to by the cloned repository's HEAD, check out the `<name>` branch instead. +--[no-]single-branch:: + Clone only the history leading to the tip of a single branch, either + specified by the `--branch` option or the primary branch remote's + `HEAD` points at. ++ +Further fetches into the resulting repository will only update the +remote-tracking branch for the branch this option was used for the initial +cloning. If the HEAD at the remote did not point at any branch when +`--single-branch` clone was made, no remote-tracking branch is created. + --[no-]full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`.
contrib/scalar/t/t9099-scalar.sh +5 -1
@@ -45,13 +45,17 @@ test_expect_success 'set up repository to clone' ' test_expect_success 'scalar clone' ' second=$(git rev-parse --verify second:second.t) && - scalar clone "file://$(pwd)" cloned && + scalar clone "file://$(pwd)" cloned --single-branch && ( cd cloned/src && git config --get --global --fixed-value maintenance.repo \ "$(pwd)" && + git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual && + echo "refs/remotes/origin/parallel" >expect && + test_cmp expect actual && + test_path_is_missing 1/2 && test_must_fail git rev-list --missing=print $second && git rev-list $second &&
Signature · 1 line
gitgitgadget
PATCH v4 11/15 scalar: allow reconfiguring an existing enlistment
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
--- contrib/scalar/scalar.c | 79 +++++++++++++++++++++----------- contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 8 ++++ 3 files changed, 67 insertions(+), 28 deletions(-)
contrib/scalar/scalar.c +51 -28
@@ -115,18 +115,20 @@ static int run_git(const char *arg, ...)return res; } -static int set_recommended_config (void ) +static int set_recommended_config (int reconfigure) {struct {const char *key;const char *value; +int overwrite_on_reconfigure; } config[] = { - {"am.keepCR" ,"true" }, - {"core.FSCache" ,"true" }, - {"core.multiPackIndex" ,"true" }, - {"core.preloadIndex" ,"true" }, +/* Required */ + {"am.keepCR" ,"true" ,1 }, + {"core.FSCache" ,"true" ,1 }, + {"core.multiPackIndex" ,"true" ,1 }, + {"core.preloadIndex" ,"true" ,1 }, #ifndef WIN32 - {"core.untrackedCache" ,"true" }, + {"core.untrackedCache" ,"true" ,1 }, #else /* * Unfortunately, Scalar's Functional Tests demonstrated
@@ -140,28 +142,29 @@ static int set_recommended_config(void) * Therefore, with a sad heart, we disable this very useful * feature on Windows. */ - { "core.untrackedCache", "false" }, + { "core.untrackedCache", "false", 1 }, #endif - { "core.logAllRefUpdates", "true" }, - { "credential.https://dev.azure.com.useHttpPath", "true" }, - { "credential.validate", "false" }, /* GCM4W-only */ - { "gc.auto", "0" }, - { "gui.GCWarning", "false" }, - { "index.threads", "true" }, - { "index.version", "4" }, - { "merge.stat", "false" }, - { "merge.renames", "false" }, - { "pack.useBitmaps", "false" }, - { "pack.useSparse", "true" }, - { "receive.autoGC", "false" }, - { "reset.quiet", "true" }, - { "feature.manyFiles", "false" }, - { "feature.experimental", "false" }, - { "fetch.unpackLimit", "1" }, - { "fetch.writeCommitGraph", "false" }, + { "core.logAllRefUpdates", "true", 1 }, + { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, + { "credential.validate", "false", 1 }, /* GCM4W-only */ + { "gc.auto", "0", 1 }, + { "gui.GCWarning", "false", 1 }, + { "index.threads", "true", 1 }, + { "index.version", "4", 1 }, + { "merge.stat", "false", 1 }, + { "merge.renames", "false", 1 }, + { "pack.useBitmaps", "false", 1 }, + { "pack.useSparse", "true", 1 }, + { "receive.autoGC", "false", 1 }, + { "reset.quiet", "true", 1 }, + { "feature.manyFiles", "false", 1 }, + { "feature.experimental", "false", 1 }, + { "fetch.unpackLimit", "1", 1 }, + { "fetch.writeCommitGraph", "false", 1 }, #ifdef WIN32 - { "http.sslBackend", "schannel" }, + { "http.sslBackend", "schannel", 1 }, #endif + /* Optional */ { "status.aheadBehind", "false" }, { "commitGraph.generationVersion", "1" }, { "core.autoCRLF", "false" },
@@ -172,7 +175,8 @@ static int set_recommended_config(void)char *value;for (i =0 ; config[i].key; i++) { -if (git_config_get_string (config[i].key, &value)) { +if ((reconfigure && config[i].overwrite_on_reconfigure) || +git_config_get_string (config[i].key, &value)) {trace2_data_string ("scalar" , the_repository, config[i].key,"created" );if (git_config_set_gently (config[i].key, config[i].value) <0 )
@@ -237,7 +241,7 @@ static int register_dir(void)int res =add_or_remove_enlistment (1 );if (!res) - res =set_recommended_config (); + res =set_recommended_config (0 );if (!res) res =toggle_maintenance (1 );
@@ -425,7 +429,7 @@ static int cmd_clone(int argc, const char **argv) (res = run_git("sparse-checkout", "init", "--cone", NULL))) goto cleanup; -if (set_recommended_config ()) +if (set_recommended_config (0 ))return error (_ ("could not configure '%s'" ), dir);if ((res =run_git ("fetch" ,"--quiet" ,"origin" ,NULL ))) {
@@ -490,6 +494,24 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_reconfigure (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +const char *const usage[] = { +N_ ("scalar reconfigure [<enlistment>]" ), +NULL + }; + + argc =parse_options (argc, argv,NULL , options, + usage,0 ); + +setup_enlistment_directory (argc, argv, usage, options,NULL ); + +return set_recommended_config (1 ); +} +static int cmd_run (int argc,const char **argv) {struct option options[] = {
@@ -626,6 +648,7 @@ static struct { { "register", cmd_register }, { "unregister", cmd_unregister }, { "run", cmd_run }, + { "reconfigure", cmd_reconfigure }, { NULL, NULL}, };
contrib/scalar/scalar.txt +8 -0
@@ -13,6 +13,7 @@ scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] +scalar reconfigure <enlistment> DESCRIPTION -----------
@@ -116,6 +117,13 @@ opinionated default settings that make Git work more efficiently with large repositories. As this task is run as part of `scalar clone` automatically, explicit invocations of this task are rarely needed. +Reconfigure +~~~~~~~~~~~ + +After a Scalar upgrade, or when the configuration of a Scalar enlistment +was somehow corrupted or changed by mistake, this subcommand allows to +reconfigure the enlistment. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
contrib/scalar/t/t9099-scalar.sh +8 -0
@@ -65,4 +65,12 @@ test_expect_success 'scalar clone' ' ) ' +test_expect_success 'scalar reconfigure' ' + git init one/src && + scalar register one && + git -C one/src config core.preloadIndex false && + scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" +' + test_done
Signature · 1 line
gitgitgadget
PATCH v4 10/15 scalar: implement the `run` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Note: this subcommand is provided primarily for backwards-compatibility, for existing Scalar uses. It is mostly just a shim for git maintenance, mapping task names from the way Scalar called them to the way Git calls them.
The reason why those names differ? The background maintenance was first implemented in Scalar, and when it was contributed as a patch series implementing the git maintenance command, reviewers suggested better names, those suggestions were accepted before the patches were integrated into core Git.
--- contrib/scalar/scalar.c | 64 +++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 19 ++++++++++++ 2 files changed, 83 insertions(+)
contrib/scalar/scalar.c +64 -0
@@ -490,6 +490,69 @@ static int cmd_register(int argc, const char **argv)return register_dir (); } +static int cmd_run (int argc,const char **argv) +{ +struct option options[] = { +OPT_END (), + }; +struct { +const char *arg, *task; + } tasks[] = { + {"config" ,NULL }, + {"commit-graph" ,"commit-graph" }, + {"fetch" ,"prefetch" }, + {"loose-objects" ,"loose-objects" }, + {"pack-files" ,"incremental-repack" }, + {NULL ,NULL } + }; +struct strbuf buf =STRBUF_INIT ; +const char *usagestr[] = {NULL ,NULL }; +int i; + +strbuf_addstr (&buf,N_ ("scalar run <task> [<enlistment>]\nTasks:\n" )); +for (i =0 ; tasks[i].arg; i++) +strbuf_addf (&buf,"\t%s\n" , tasks[i].arg); + usagestr[0 ] = buf.buf; + + argc =parse_options (argc, argv,NULL , options, + usagestr,0 ); + +if (!argc) +usage_with_options (usagestr, options); + +if (!strcmp ("all" , argv[0 ])) { + i =-1 ; + }else { +for (i =0 ; tasks[i].arg &&strcmp (tasks[i].arg, argv[0 ]); i++) + ;/* keep looking for the task */ + +if (i >0 && !tasks[i].arg) { +error (_ ("no such task: '%s'" ), argv[0 ]); +usage_with_options (usagestr, options); + } + } + + argc--; + argv++; +setup_enlistment_directory (argc, argv, usagestr, options,NULL ); +strbuf_release (&buf); + +if (i ==0 ) +return register_dir (); + +if (i >0 ) +return run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL ); + +if (register_dir ()) +return -1 ; +for (i =1 ; tasks[i].arg; i++) +if (run_git ("maintenance" ,"run" , +"--task" , tasks[i].task,NULL )) +return -1 ; +return 0 ; +} +static int remove_deleted_enlistment (struct strbuf *path) {int res =0 ;
@@ -562,6 +625,7 @@ static struct { { "list", cmd_list }, { "register", cmd_register }, { "unregister", cmd_unregister }, + { "run", cmd_run }, { NULL, NULL}, };
contrib/scalar/scalar.txt +19 -0
@@ -12,6 +12,7 @@ scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<e scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>] DESCRIPTION -----------
@@ -97,6 +98,24 @@ unregister [<enlistment>]:: Remove the specified repository from the list of repositories registered with Scalar and stop the scheduled background maintenance. +Run +~~~ + +scalar run ( all | config | commit-graph | fetch | loose-objects | pack-files ) [<enlistment>]:: + Run the given maintenance task (or all tasks, if `all` was specified). + Except for `all` and `config`, this subcommand simply hands off to + linkgit:git-maintenance[1] (mapping `fetch` to `prefetch` and + `pack-files` to `incremental-repack`). ++ +These tasks are run automatically as part of the scheduled maintenance, +as soon as the repository is registered with Scalar. It should therefore +not be necessary to run this subcommand manually. ++ +The `config` task is specific to Scalar and configures all those +opinionated default settings that make Git work more efficiently with +large repositories. As this task is run as part of `scalar clone` +automatically, explicit invocations of this task are rarely needed. + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
Signature · 1 line
gitgitgadget