Upstreaming the Scalar command
Subsystems (2)
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
Re: [PATCH v4 04/15] scalar: 'register' sets recommended config and starts maintenance
On Tue, Sep 14, 2021 at 7:39 AM Derrick Stolee via GitGitGadget <[email protected]> wrote:
...
Quoted 17 lines
contrib/scalar/scalar.c +17
-0+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
contrib/scalar/scalar.c line 143
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ * 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
Interesting. (I'm somewhat leery of the untrackedCache just knowing that it used to operate despite an exponential number of visits to files (exponential in depth of directories) and getting different answers with different visits, making me feel like it was black magic that it ever worked and wondering what kind of corner case issues still lurk with it. See e.g.
https://lore.kernel.org/git/CABPp-BFiwzzUgiTj_zu+vF5x20L0=1cf25cHwk7KZQj2YkVzXw@mail.gmail.com/)
contrib/scalar/scalar.c line 145
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+ { "core.logAllRefUpdates", "true" }, + { "credential.https://dev.azure.com.useHttpPath", "true" },
Not only opinionated, but special configuration for certain sites? I'm not complaining, just slightly surprised.
contrib/scalar/scalar.c line 150
Quoted 5 lines
contrib/scalar/scalar.c +5
-0+ { "credential.validate", "false" }, /* GCM4W-only */ + { "gc.auto", "0" }, + { "gui.GCWarning", "false" }, + { "index.threads", "true" }, + { "index.version", "4" },
I take it your users don't make use of jgit? (Users aren't using jgit directly here, at least not to my knowledge, but multiple gradle plugins do.) I tried turning this on a while back, and quickly got multiple reports of problems because jgit didn't understand the index. I had to turn it off and send out various PSAs on how to recover.
contrib/scalar/scalar.c line 152
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+ { "merge.stat", "false" }, + { "merge.renames", "false" },
Is this just historical and not needed anymore, is it here just for a little longer and you are planning on transitioning away from this, or are you still set on this setting?
Quoted 1 line
+ { "pack.useBitmaps", "false" },
I don't understand anything bitmap related, but I thought they were performance related, so I'm surprised by this one. Is there a reason for this one? (Is it handled by maintenance instead?)
contrib/scalar/scalar.c line 157
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+ { "pack.useSparse", "true" }, + { "receive.autoGC", "false" }, + { "reset.quiet", "true" }, + { "feature.manyFiles", "false" },
If you simply set core.untrackedCache to false _after_ setting feature.manyFiles to true, would it make sense to switch this? (Or does it matter, since you've already individually set all the config settings that this one would set?)
contrib/scalar/scalar.c line 169
Quoted 12 lines
contrib/scalar/scalar.c +12
-0+ { "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 }, + };
Are there easy-ish ways for other groups of users to adopt scalar but change the list of config settings (e.g. index.version and merge.renames) in some common way for all those users?
Re: [PATCH v4 04/15] scalar: 'register' sets recommended config and starts maintenance
Quoted 8 lines · Elijah Newren
On Tue, Sep 14, 2021 at 7:39 AM Derrick Stolee via GitGitGadget <[email protected]> wrote:
...Quoted 1 line
+ { "pack.useBitmaps", "false" },
I don't understand anything bitmap related, but I thought they were performance related, so I'm surprised by this one. Is there a reason for this one? (Is it handled by maintenance instead?)
I don't know why Derrick did this, but there's still AFAIK cases where bitmaps are worse than not in the context of a client (the scalar use-case), see the rabbit hole starting at:
https://lore.kernel.org/git/[email protected]/
Re: [PATCH v4 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Elijah NewrenHi Elijah,
Quoted 31 lines · Elijah Newren
On Tue, Sep 14, 2021 at 7:39 AM Derrick Stolee via GitGitGadget <[email protected]> wrote:
...Quoted 17 lines
contrib/scalar/scalar.c +17
-0+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 thecontrib/scalar/scalar.c line 143
Quoted 10 lines
contrib/scalar/scalar.c +10
-0+ * 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
Quoted 7 lines
Interesting. (I'm somewhat leery of the untrackedCache just knowing that it used to operate despite an exponential number of visits to files (exponential in depth of directories) and getting different answers with different visits, making me feel like it was black magic that it ever worked and wondering what kind of corner case issues still lurk with it. See e.g.
https://lore.kernel.org/git/CABPp-BFiwzzUgiTj_zu+vF5x20L0=1cf25cHwk7KZQj2YkVzXw@mail.gmail.com/)
The implementation of the untracked cache certainly is quite a challenge to wrap one's head around, for sure. However, it does manage to speed up operations substantially (when it works).
The real fun starts when you turn on the FSMonitor, though. Then it is reliable, all of a sudden! The reason seems to be some sort of delayed lastModified (AKA mtime) evaluation which is somehow triggered by FSMonitor ;-)
So in microsoft/git, where we include FSMonitor and turn it on as part of scalar clone, we also enable the untracked cache, for noticeably happier users.
Quoted 5 lines
contrib/scalar/scalar.c line 145
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+ { "core.logAllRefUpdates", "true" }, + { "credential.https://dev.azure.com.useHttpPath", "true" },Not only opinionated, but special configuration for certain sites? I'm not complaining, just slightly surprised.
Yes. I am not aware of other sites where you would want to use different credentials depending on the URL path, but Azure DevOps definitely is such a site, and therefore needs useHttpPath. Rather than requiring users to know this, we set it for them.
Quoted 7 lines
contrib/scalar/scalar.c line 150
Quoted 5 lines
contrib/scalar/scalar.c +5
-0+ { "credential.validate", "false" }, /* GCM4W-only */ + { "gc.auto", "0" }, + { "gui.GCWarning", "false" }, + { "index.threads", "true" }, + { "index.version", "4" },I take it your users don't make use of jgit?
Nope ;-) I doubt that the features we use to make Git scalable are implemented in JGit.
Quoted 5 lines
(Users aren't using jgit directly here, at least not to my knowledge, but multiple gradle plugins do.) I tried turning this on a while back, and quickly got multiple reports of problems because jgit didn't understand the index. I had to turn it off and send out various PSAs on how to recover.
TBH it gives me shivers of dread thinking about large repositories/worktrees being handled within a Java VM. The amount of, let's call it "non-canonical" code, required by JGit to make it somewhat performant, is staggering. Just think about the way you have to emulate mmap()ing part of a packfile and interpreting it as a packed C struct. I forgot the details, of course, and I am quite glad that I did.
Quoted 6 lines
contrib/scalar/scalar.c line 152
Quoted 2 lines
contrib/scalar/scalar.c +2
-0+ { "merge.stat", "false" }, + { "merge.renames", "false" },Is this just historical and not needed anymore, is it here just for a little longer and you are planning on transitioning away from this, or are you still set on this setting?
It is here mostly for historical reasons.
Quoted 5 lines
Quoted 1 line
+ { "pack.useBitmaps", "false" },
I don't understand anything bitmap related, but I thought they were performance related, so I'm surprised by this one. Is there a reason for this one? (Is it handled by maintenance instead?)
Again, this is here for historical reasons. Scalar sets this, and my goal with this patch series is to port it from .NET to C. So I did not question the reasoning.
My _guess_ however is that bitmaps really only work well when everything is in one single pack. Which is rather not the case with Scalar
enlistments: they are way too large to be repacked all the time.
Quoted 9 lines
contrib/scalar/scalar.c line 157
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+ { "pack.useSparse", "true" }, + { "receive.autoGC", "false" }, + { "reset.quiet", "true" }, + { "feature.manyFiles", "false" },If you simply set core.untrackedCache to false _after_ setting feature.manyFiles to true, would it make sense to switch this? (Or does it matter, since you've already individually set all the config settings that this one would set?)
Frankly, I was a bit puzzled why feature.manyFiles was set to false. The rationale is explained in
https://github.com/microsoft/scalar/commit/2fc84dba9c95:
The feature.* config settings change the defaults for some other config settings. We already monitor config settings pretty carefully, so let's disable these.
As to switching this, it shouldn't matter. The idea of feature.* is to set defaults, but not override any explicitly configured settings.
Quoted 8 lines
Quoted 8 lines
contrib/scalar/scalar.c +8
-0+ { "feature.experimental", "false" }, + { "fetch.unpackLimit", "1" }, + { "fetch.writeCommitGraph", "false" }, +#ifdef WIN32 + { "http.sslBackend", "schannel" }, +#endif + { "status.aheadBehind", "false" }, + { "commitGraph.generationVersion", "1" },
Quoted 8 lines
contrib/scalar/scalar.c line 169
Quoted 4 lines
contrib/scalar/scalar.c +4
-0+ { "core.autoCRLF", "false" }, + { "core.safeCRLF", "false" }, + { NULL, NULL }, + };Are there easy-ish ways for other groups of users to adopt scalar but change the list of config settings (e.g. index.version and merge.renames) in some common way for all those users?
Not in Scalar.
I would hope, however, that we could figure out ways to make this more configurable when re-implementing this functionality in core Git. I have a couple ideas, but nothing fleshed out, and besides, I do not want to think too far ahead, I already made that mistake and then got bogged down in discussions about minimal vs non-minimal changes in the top-level Makefile ;-)
So yeah, good point, but it's probably not a good time yet to discuss this tangent.
Thank you for reviewing,
Dscho
Re: [PATCH v4 04/15] scalar: 'register' sets recommended config and starts maintenance
In reply to Derrick Stolee via GitGitGadgetSorry, one more thing...
On Tue, Sep 14, 2021 at 7:39 AM Derrick Stolee via GitGitGadget <[email protected]> wrote:
...
contrib/scalar/scalar.c line 53
Quoted 3 lines
contrib/scalar/scalar.c +3
-0+/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) {
...and...
contrib/scalar/scalar.c line 68
Quoted 3 lines
contrib/scalar/scalar.c +3
-0+/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) {
Do these two checks suggest that only a primary worktree can be enlisted with scalar? (Is git-worktree generally incompatible?)
Re: [PATCH v4 04/15] scalar: 'register' sets recommended config and starts maintenance
Hi Elijah,
Quoted 9 lines · Elijah Newren
Sorry, one more thing...
On Tue, Sep 14, 2021 at 7:39 AM Derrick Stolee via GitGitGadget <[email protected]> wrote:
...contrib/scalar/scalar.c line 53
Quoted 3 lines
contrib/scalar/scalar.c +3
-0+/* check if currently in enlistment root with src/ workdir */ +strbuf_addstr (&path,"/src/.git" ); +if (is_git_directory (path.buf)) {
Quoted 8 lines
...and...
contrib/scalar/scalar.c line 68
Quoted 3 lines
contrib/scalar/scalar.c +3
-0+/* check if currently in workdir */ +strbuf_addstr (&path,"/.git" ); +if (is_git_directory (path.buf)) {Do these two checks suggest that only a primary worktree can be enlisted with scalar? (Is git-worktree generally incompatible?)
Good point! I think we'll need to use is_nonbare_repository_dir() instead.
This also has the additional benefit of doing away with quite a bit of /.git appending and undoing it. I.e. it simplifies the code dramatically.
Ciao,
Dscho
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 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 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
Re: [PATCH v4 11/15] scalar: allow reconfiguring an existing enlistment
Quoted 83 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
Signed-off-by Johannes Schindelin <[email protected]>--- 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 +24
-24@@ -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 },
contrib/scalar/scalar.c static int set_recommended_config(void) · line 170
Quoted 10 lines
contrib/scalar/scalar.c +4
-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" },
Now you have optional settings...but index.version and merge.renames aren't among them?? Why are those required? (...and to go a step further; should merge.renames even be off in a merge-ort world?)
Re: [PATCH v4 11/15] scalar: allow reconfiguring an existing enlistment
Hi Elijah,
Quoted 91 lines · Elijah Newren
Quoted 89 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This comes in handy during Scalar upgrades, or when config settings were messed up by mistake.
Signed-off-by Johannes Schindelin <[email protected]>--- 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 +27
-25@@ -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
Quoted 8 lines
contrib/scalar/scalar.c static int set_recommended_config(void) · line 170
Quoted 4 lines
contrib/scalar/scalar.c +1
-0+/* Optional */ { "status.aheadBehind", "false" }, { "commitGraph.generationVersion", "1" }, { "core.autoCRLF", "false" },Now you have optional settings...but index.version and merge.renames aren't among them?? Why are those required? (...and to go a step further; should merge.renames even be off in a merge-ort world?)
I think the idea here is that they are required so that a scalar reconfigure will set them, even if the current enlistment had been created by a previous Scalar version that had _not_ set those.
And yes, in a merge-ort world, merge.renames should probably be forced to true, again because it is in the "Required" section.
Ciao,
Dscho
PATCH v4 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 v4 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
Re: [PATCH v4 13/15] scalar: implement the `delete` command
Quoted 11 lines · Matthew John Cheetham via GitGitGadget
From: 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).
But if the current directory is inside the enlistment's directory, didn't that happen because the parent process' current directory was inside the enlistment directory? Or was there some kind of directory switching that scalar itself was doing causing it to be inside the enlistment directory?
If the the current directory was inside the enlistment's directory because it inherited a parent process' current directory, wouldn't that also prevent deleting it? If so, should there be a special check for that case and pre-emptively returning an error rather than attempting the recursive directory deletion and just spitting out an error when it fails?
(Also seems slightly related to
https://github.com/gitgitgadget/git/pull/1037, which I'll submit as soon as en/removing_untracked_fixes hits next.)
Quoted 143 lines
Co-authored-by Victoria Dye <[email protected]>Signed-off-by Matthew John Cheetham <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>--- 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
-1@@ -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 --gitgitgadget
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH v4 13/15] scalar: implement the `delete` command
Hi Matthew and Elijah,
Quoted 18 lines · Matthew Cheetham
Quoted 5 lines · Elijah Newren
But if the current directory is inside the enlistment's directory, didn't that happen because the parent process' current directory was inside the enlistment directory? Or was there some kind of directory switching that scalar itself was doing causing it to be inside the enlistment directory?
Yes to the latter.
setup_enlistment_directorychanges the current directory much likesetup_git_directory.Quoted 6 lines
If the the current directory was inside the enlistment's directory because it inherited a parent process' current directory, wouldn't that also prevent deleting it? If so, should there be a special check for that case and pre-emptively returning an error rather than attempting the recursive directory deletion and just spitting out an error when it fails?
Quoted 2 lines
You are correct. Speaking to Johannes about this I believe he is looking to add a check/error in a new patch series revision.
Indeed, I did notice GGG#1037, and I changed the code so that it detects whether scalar delete was called from within the enlistment, and refuses to run in that case. Users will have to call scalar delete <path> from outside the enlistment.
Ciao,
Dscho
PATCH v4 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 v4 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
Hi,
Quoted 11 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.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
Forgot to say:
* Sent this out to reflect my latest state before taking off for two weeks.
Ciao,
Dscho
Quoted 16 lines · Johannes Schindelin
Hi,
Quoted 11 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.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
Quoted 4 lines
Forgot to say:
* Sent this out to reflect my latest state before taking off for two weeks.
Thanks, will queue.
Have fun.
v5 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.
Changes since v4:
* scalar delete now refuses to delete anything if it was started from within the enlistment.
* scalar delete releases any handles to the object store before deleting the enlistment.
* The OBJECTS list in the Makefile will now include Scalar.
* scalar register now supports secondary worktrees, in addition to the primary worktree.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
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
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 | 9 + contrib/scalar/.gitignore | 5 + contrib/scalar/Makefile | 57 +++ contrib/scalar/scalar.c | 845 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 154 ++++++ 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-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v4:
1: 852ec003109 ! 1: 7119a8efc21 scalar: create a rudimentary executable
@@ Commit message
## Makefile ##
-@@ Makefile: endif
- .PHONY: objects
- objects: $(OBJECTS)
-
+@@ Makefile: OBJECTS += $(FUZZ_OBJS)
+ ifndef NO_CURL
+ OBJECTS += http.o http-walker.o remote-curl.o
+ endif
++
+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))))
+ .PHONY: objects
+ objects: $(OBJECTS)
@@ Makefile: $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
2: 6ab9c7195da = 2: edc1e5f73bd scalar: start documenting the command
3: 14992033d7c = 3: 9eeb66d5b5d scalar: create test infrastructure
4: bbbc4c33390 ! 4: 8ebfa51ae15 scalar: 'register' sets recommended config and starts maintenance
@@ contrib/scalar/scalar.c
+ 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");
-+
++ strbuf_addstr(&path, "/src");
++ if (is_nonbare_repository_dir(&path)) {
+ if (enlistment_root)
+ strbuf_add(enlistment_root, path.buf, len);
+
@@ contrib/scalar/scalar.c
+ strbuf_setlen(&path, len);
+
+ /* check if currently in workdir */
-+ strbuf_addstr(&path, "/.git");
-+ if (is_git_directory(path.buf)) {
-+ strbuf_setlen(&path, len);
-+
++ if (is_nonbare_repository_dir(&path)) {
+ if (enlistment_root) {
+ /*
+ * If the worktree's directory's name is `src`, the enlistment is the
@@ contrib/scalar/scalar.c
+ enlistment_found = 1;
+ break;
+ }
-+
-+ strbuf_setlen(&path, len);
+ } while (strbuf_parent_directory(&path));
+
+ if (!enlistment_found)
5: eadcddb2a9b = 5: 51b5fc577c9 scalar: 'unregister' stops background maintenance
6: c3c2f3a4971 = 6: 0041477374e scalar: let 'unregister' handle a deleted enlistment directory gracefully
7: 90ef9b826b2 = 7: 12efa86e4cb scalar: implement 'scalar list'
8: 79cde4417d8 = 8: 670891d14cc scalar: implement the `clone` subcommand
9: 0acdaeb7396 = 9: 03b8385ebb5 scalar: teach 'clone' to support the --single-branch option
10: 64e3403ac12 = 10: 5a0b3843f98 scalar: implement the `run` command
11: ada242c7c8c = 11: 6d9aaeb05fa scalar: allow reconfiguring an existing enlistment
12: 5c11117da51 = 12: 2967d7f1425 scalar: teach 'reconfigure' to optionally handle all registered enlistments
13: 914c16c7fcd ! 13: 8069cc536fe scalar: implement the `delete` command
@@ contrib/scalar/scalar.c
#include "run-command.h"
#include "refs.h"
+#include "dir.h"
++#include "packfile.h"
/*
* Remove the deepest subdirectory in the provided path string. Path must not
@@ contrib/scalar/scalar.c: static int cmd_unregister(int argc, const char **argv)
+static int cmd_delete(int argc, const char **argv)
+{
++ char *cwd = xgetcwd();
+ struct option options[] = {
+ OPT_END(),
+ };
@@ contrib/scalar/scalar.c: static int cmd_unregister(int argc, const char **argv)
+
+ setup_enlistment_directory(argc, argv, usage, options, &enlistment);
+
-+ res = delete_enlistment(&enlistment);
++ if (dir_inside_of(cwd, enlistment.buf) >= 0)
++ res = error(_("refusing to delete current working directory"));
++ else {
++ close_object_store(the_repository->objects);
++ res = delete_enlistment(&enlistment);
++ }
+ strbuf_release(&enlistment);
++ free(cwd);
+
+ return res;
+}
14: 06995770120 ! 14: 2ecfaa5d0fe scalar: implement the `version` command
@@ Commit message
## contrib/scalar/scalar.c ##
@@
- #include "run-command.h"
#include "refs.h"
#include "dir.h"
+ #include "packfile.h"
+#include "help.h"
/* 15: 7539725bb4f = 15: f81e8b3bcf1 scalar: accept -C and -c options before the subcommand
Signature · 1 line
gitgitgadget
PATCH v5 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 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 | 9 +++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +9 -0
@@ -2444,6 +2444,11 @@ OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif + +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + .PHONY: objects objects: $(OBJECTS)
@@ -2586,6 +2591,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 v5 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 v5 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 v5 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 | 248 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 265 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +248 -0
@@ -5,11 +5,259 @@#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" ); +if (is_nonbare_repository_dir (&path)) { +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 */ +if (is_nonbare_repository_dir (&path)) { +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 ; + } + }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 v5 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
@@ -197,12 +197,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;
@@ -213,24 +213,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; }
@@ -253,11 +268,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 v5 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
@@ -268,6 +268,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[] = {
@@ -281,6 +299,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 v5 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
@@ -250,6 +250,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[] = {
@@ -336,6 +346,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 v5 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
@@ -250,6 +251,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 )
@@ -346,6 +546,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 v5 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
@@ -326,12 +326,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[] = {
@@ -402,7 +405,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 v5 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
@@ -483,6 +483,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 ;
@@ -555,6 +618,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 v5 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
@@ -108,18 +108,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
@@ -133,28 +135,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" },
@@ -165,7 +168,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 )
@@ -230,7 +234,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 );
@@ -418,7 +422,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 ))) {
@@ -483,6 +487,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[] = {
@@ -619,6 +641,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 v5 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
@@ -487,22 +487,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 v5 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 | 63 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 9 +++++ 3 files changed, 80 insertions(+)
contrib/scalar/scalar.c +63 -0
@@ -8,6 +8,8 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" +#include "packfile.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -327,6 +329,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 ;
@@ -687,6 +716,39 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +char *cwd =xgetcwd (); +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); + +if (dir_inside_of (cwd, enlistment.buf) >=0 ) + res =error (_ ("refusing to delete current working directory" )); +else { +close_object_store (the_repository->objects); + res =delete_enlistment (&enlistment); + } +strbuf_release (&enlistment); +free (cwd); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -697,6 +759,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 v5 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
@@ -10,6 +10,7 @@#include "refs.h" #include "dir.h" #include "packfile.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -356,6 +357,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 ;
@@ -749,6 +759,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 **);
@@ -760,6 +798,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
PATCH v5 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
@@ -807,6 +807,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--;
@@ -817,7 +836,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
Quoted 1 line · Johannes Schindelin via GitGitGadget
* The OBJECTS list in the
Makefilewill now include Scalar.
So that looks like a partial fix for what I brought up in [1] [...]
Quoted 16 lines
Range-diff vs v4:
1:
852ec003109! 1:7119a8efc21scalar: create a rudimentary executable
@@ Commit messageSigned-off-by Johannes Schindelin <[email protected]>##
Makefile##
-@@Makefile: endif
- .PHONY: objects
- objects: $(OBJECTS)
-
+@@Makefile: OBJECTS += $(FUZZ_OBJS)
+ ifndefNO_CURL
+ OBJECTS += http.o http-walker.o remote-curl.o
+ endif
++
Quoted 8 lines
+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))))
+ .PHONY: objects
+ objects: $(OBJECTS)
Except that this & contrib/scalar/Makefile is still broken in multiple ways. We now have two Makefiles that can build contrib/scalar/scalar:
touch advice.h; make -j8 contrib/scalar/scalar
But try:
$ touch advice.h; (cd contrib/scalar 2>/dev/null && make scalar)
make: 'scalar' is up to date.I.e. (I'm presuming in response to what I brought up in [1]) the depenency graph in the top-level Makefile is correct in this specific area. But it understands the ".depends" files (depending on COMPUTE_HEADER_DEPENDENCIES), your sub-Makefile doesn't.
There's similar whack-a-mole issues in other areas, e.g.:
make -C contrib/scalar/ test
Will break or not depending on whether you've built the top-level git.
I noticed at least one other subtle breakage (first thing I checked after those two).
I'm happy to send you a working patch to integrate that fixes all these issues, it also integrates with "make install", this series leaves us with a "scalar" binary, but no way to install it, if we just piggy-back on the existing installation procedure.
The side-thread on the v3[3] that you most recently replied to is conflating some suggestion of shipping this as a built-in, with the purely build-system implementation details I'm suggesting here.
I did mention using it as a built-in in [4], but for the semi-related issue of scalar.c copy/pasting less code from git.c. But that was in the context of such a thing being purely a non-visible implementation detail. I.e. it would still be "scalar", not "git scalar".
*That* suggestion is just a side-musing about whether it would be easier to teach git.c to inspects its argv and have a special-case for dispatching to cmd_scalar(), a user would never know the difference. I think that might also be worthwhile, but I care *way* less about that than making maintaining the Makefile a hassle, and it's an entirely orthogonal suggestion.
1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/
3. https://lore.kernel.org/git/[email protected]
4. https://lore.kernel.org/git/[email protected]/
v6 cover Upstreaming the Scalar command
In reply to Johannes Schindelin via GitGitGadgettl;dr: This series contributes the core part of the Scalar command to the Git project. This command provides an opinionated way to create and configure Git repositories with a focus on very large repositories.
Changes since v5:
* Fixed the commit message talking about make -C contrib/scalar/Makefile.
* Fixed the git ls-tree invocation suggested in the manual for scalar clone.
* Invoking make -C contrib/scalar, then changing a source file of libgit.a and then immediately invoking make -C contrib/scalar again will now implicitly rebuild libgit.a.
Changes since v4:
* scalar delete now refuses to delete anything if it was started from within the enlistment.
* scalar delete releases any handles to the object store before deleting the enlistment.
* The OBJECTS list in the Makefile will now include Scalar.
* scalar register now supports secondary worktrees, in addition to the primary worktree.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
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
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/ (it offers a Git for Windows installer, a macOS package and an Ubuntu package, Scalar has been included since v2.33.0.vfs.0.0).
Next steps
==========
I have lined up a few more patch series on top of this one:
1. Implement a scalar diagnose command.
2. Use the built-in FSMonitor (that patch series obviously needs to wait for FSMonitor to be integrated).
3. Modify the config machinery to be more generous about concurrent writes, say, to the user-wide config.
4. A few patches to optionally build and install scalar as part of a regular Git install (also teaching git help scalar to find the Scalar documentation
These are included in my vfs-with-scalar branch thicket
[https://github.com/dscho/git/commits/vfs-with-scalar]. On top of that, this branch thicket also includes patches I do not plan on upstreaming, mainly because they are too specific either to VFS for Git, or they support Azure Repos (which does not offer partial clones but speaks the GVFS protocol, which can be used to emulate partial clones).
One other thing is very interesting about that vfs-with-scalar branch
thicket: it contains a GitHub workflow which will run Scalar's quite extensive Functional Tests suite. This test suite is quite comprehensive and caught us a lot of bugs in the past, not only in the Scalar code, but also core Git.
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 core Git, even 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 in the first place, 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.
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 | 9 + contrib/scalar/.gitignore | 5 + contrib/scalar/Makefile | 57 +++ contrib/scalar/scalar.c | 845 +++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 155 ++++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 ++++ 7 files changed, 1237 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-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v5:
1: 7119a8efc21 ! 1: 9b0b00438ec scalar: create a rudimentary executable
@@ Commit message
The idea here is that you can (optionally) build Scalar via
- make -C contrib/scalar/Makefile
+ make -C contrib/scalar/
This will build the `scalar` executable and put it into the
contrib/scalar/ subdirectory.
@@ contrib/scalar/Makefile (new)
+TARGETS = scalar$(X) scalar.o
+GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a
+
-+all: scalar$X
++all: scalar$(X)
+
+$(GITLIBS):
+ $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ contrib/scalar/Makefile (new)
+clean:
+ $(RM) $(TARGETS)
+
-+.PHONY: all clean FORCE
++.PHONY: $(GITLIBS) all clean FORCE
## contrib/scalar/scalar.c (new) ##
@@
2: edc1e5f73bd ! 2: 40fee75968e scalar: start documenting the command
@@ contrib/scalar/Makefile: $(TARGETS): $(GITLIBS) scalar.c
$(RM) $(TARGETS)
+ $(RM) scalar.1 scalar.html scalar.xml
--.PHONY: all clean FORCE
+-.PHONY: $(GITLIBS) all clean FORCE
+docs: scalar.html scalar.1
+
+scalar.html: | scalar.1 # prevent them from trying to build `doc.dep` in parallel
@@ contrib/scalar/Makefile: $(TARGETS): $(GITLIBS) scalar.c
+ ../contrib/scalar/$@
+ $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ .
+
-+.PHONY: all clean docs FORCE
++.PHONY: $(GITLIBS) all clean docs FORCE
## contrib/scalar/scalar.txt (new) ##
@@
3: 9eeb66d5b5d ! 3: e3507c2d5f7 scalar: create test infrastructure
@@ contrib/scalar/Makefile: 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
+-all: scalar$(X)
++all: scalar$(X) ../../bin-wrappers/scalar
$(GITLIBS):
$(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@)
@@ contrib/scalar/Makefile: 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
+-.PHONY: $(GITLIBS) all clean docs FORCE
++.PHONY: $(GITLIBS) all clean docs test FORCE
## contrib/scalar/t/Makefile (new) ##
@@
4: 8ebfa51ae15 = 4: 53224e506ea scalar: 'register' sets recommended config and starts maintenance
5: 51b5fc577c9 = 5: 3591e53700b scalar: 'unregister' stops background maintenance
6: 0041477374e = 6: fe04ae7ec66 scalar: let 'unregister' handle a deleted enlistment directory gracefully
7: 12efa86e4cb = 7: 136aec439fd scalar: implement 'scalar list'
8: 670891d14cc ! 8: ed24a5e9b4c scalar: implement the `clone` subcommand @@ contrib/scalar/scalar.txt: an existing Git worktree with Scalar whose name is no
+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. ++subdirectories outside your sparse-checkout by using git ls-tree ++HEAD[:<directory>].
+
+-b <name>::
+--branch <name>::
9: 03b8385ebb5 ! 9: 5ee54a9e25f scalar: teach 'clone' to support the --single-branch option
@@ contrib/scalar/scalar.txt: scalar - an opinionated repository management tool
scalar list
scalar register [<enlistment>]
scalar unregister [<enlistment>]
-@@ contrib/scalar/scalar.txt: subdirectories outside your sparse-checkout by using git ls-tree HEAD. +@@ contrib/scalar/scalar.txt: HEAD[:<directory>]`.
Instead of checking out the branch pointed to by the cloned
repository's HEAD, check out the <name> branch instead.
10: 5a0b3843f98 = 10: 3361eb8f091 scalar: implement the `run` command 11: 6d9aaeb05fa = 11: c4778c1dc5f scalar: allow reconfiguring an existing enlistment 12: 2967d7f1425 = 12: cd824e9e483 scalar: teach 'reconfigure' to optionally handle all registered enlistments 13: 8069cc536fe = 13: 843026d5481 scalar: implement the `delete` command 14: 2ecfaa5d0fe = 14: 5ca169b3f3a scalar: implement the `version` command 15: f81e8b3bcf1 = 15: e3a6eea0534 scalar: accept -C and -c options before the subcommand
Signature · 1 line
gitgitgadget
PATCH v6 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 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/
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 | 9 +++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +9 -0
@@ -2444,6 +2444,11 @@ OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif + +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + .PHONY: objects objects: $(OBJECTS)
@@ -2586,6 +2591,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: $(GITLIBS) 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 v6 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: $(GITLIBS) 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: $(GITLIBS) 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 v6 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: $(GITLIBS) all clean docs FORCE +.PHONY: $(GITLIBS) 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 v6 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 | 248 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 265 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +248 -0
@@ -5,11 +5,259 @@#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" ); +if (is_nonbare_repository_dir (&path)) { +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 */ +if (is_nonbare_repository_dir (&path)) { +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 ; + } + }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 v6 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
@@ -197,12 +197,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;
@@ -213,24 +213,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; }
@@ -253,11 +268,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 v6 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
@@ -268,6 +268,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[] = {
@@ -281,6 +299,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 v6 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
@@ -250,6 +250,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[] = {
@@ -336,6 +346,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 v6 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 | 32 ++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 262 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
@@ -250,6 +251,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 )
@@ -346,6 +546,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 +29 -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,37 @@ 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[:<directory>]`. + +-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 +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 v6 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
@@ -326,12 +326,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[] = {
@@ -402,7 +405,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>]
@@ -57,6 +57,16 @@ HEAD[:<directory>]`. 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 v6 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
@@ -483,6 +483,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 ;
@@ -555,6 +618,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 v6 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
@@ -108,18 +108,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
@@ -133,28 +135,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" },
@@ -165,7 +168,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 )
@@ -230,7 +234,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 );
@@ -418,7 +422,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 ))) {
@@ -483,6 +487,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[] = {
@@ -619,6 +641,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 v6 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
@@ -487,22 +487,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 --------
@@ -125,6 +125,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 v6 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 | 63 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 9 +++++ 3 files changed, 80 insertions(+)
contrib/scalar/scalar.c +63 -0
@@ -8,6 +8,8 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" +#include "packfile.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -327,6 +329,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 ;
@@ -687,6 +716,39 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +char *cwd =xgetcwd (); +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); + +if (dir_inside_of (cwd, enlistment.buf) >=0 ) + res =error (_ ("refusing to delete current working directory" )); +else { +close_object_store (the_repository->objects); + res =delete_enlistment (&enlistment); + } +strbuf_release (&enlistment); +free (cwd); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -697,6 +759,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 -----------
@@ -128,6 +129,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 v6 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
@@ -10,6 +10,7 @@#include "refs.h" #include "dir.h" #include "packfile.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -356,6 +357,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 ;
@@ -749,6 +759,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 **);
@@ -760,6 +798,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
PATCH v6 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
@@ -807,6 +807,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--;
@@ -817,7 +836,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
Quoted 3 lines · Johannes Schindelin via GitGitGadget
tl;dr: This series contributes the core part of the Scalar command to the Git project. This command provides an opinionated way to create and configure Git repositories with a focus on very large repositories.
I started a discussion [1] to help answer some big questions about this series, but to separate those thoughts from the concrete patches. If you are interested, please contribute feedback there.
[1] https://lore.kernel.org/git/[email protected]/
Thanks,
-Stolee
v7 cover Upstreaming the Scalar command
In reply to Johannes Schindelin via GitGitGadgettl;dr: This series contributes the core part of the Scalar command to the Git project. This command provides an opinionated way to create and configure Git repositories with a focus on very large repositories.
Changes since v6:
* Rebased on top of v2.34.0.
* Inserted a commit that adds contrib/scalar/README.md, containing the roadmap of what I have planned for Scalar.
* The Scalar test's definition of GIT_TEST_MAINT_SCHEDULER has been adjusted to accommodate for a change in v2.32.0..v2.34.0.
* The config setting defaults now include fetch.showForcedUpdates=false, which has been identified as helping with a performance issue in large repositories.
* To avoid mistaking the current patch series for being feature-complete enough to unleash onto end users, I moved the Makefile rules to build HTML/manual pages to a later patch series.
* The patch that adds support for -c <key>=<value> and -C <directory> was moved to its own add-on patch series: While it is obvious that those options are valuable to have, an open question is whether there are other "pre-command" options in git that would be useful, too, and I would like to postpone that discussion to that date.
* I added two patches that I had planned on keeping in an add-on patch series for later, to build and test Scalar as part of the CI. I am still not 100% certain that it is a good idea to do so already now, but let's see what the reviewers have to say.
Changes since v5:
* Fixed the commit message talking about make -C contrib/scalar/Makefile.
* Fixed the git ls-tree invocation suggested in the manual for scalar clone.
* Invoking make -C contrib/scalar, then changing a source file of libgit.a and then immediately invoking make -C contrib/scalar again will now implicitly rebuild libgit.a.
Changes since v4:
* scalar delete now refuses to delete anything if it was started from within the enlistment.
* scalar delete releases any handles to the object store before deleting the enlistment.
* The OBJECTS list in the Makefile will now include Scalar.
* scalar register now supports secondary worktrees, in addition to the primary worktree.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
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
Background
==========
Microsoft invested a lot of effort into scaling Git to the needs of the Windows operating system source code. Based on the experience of the first approach, VFS for Git, the Scalar project was started. Scalar specifically has as its core goal to funnel all improvements into core Git.
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 scheduled 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/ (it offers a Git for Windows installer, a macOS package and an Ubuntu package, Scalar has been included since v2.33.0.vfs.0.0).
Next steps
==========
Since there are existing Scalar users, I want to ensure backwards-compatibility with its existing command-line interface. Keeping that in mind, everything in this series is up for discussion.
I obviously believe that Scalar brings a huge benefit, and think that it would be ideal for all of Scalar's learnings to end up in git clone/git init/git maintenance eventually. It is also conceivable, however, that the scalar command could graduate to be a core part of Git at some stage in the future (such a decision would probably depend highly on users' feedback). See also the discussion about the architecture of Scalar
[https://lore.kernel.org/git/[email protected]/],
kicked off by Stolee.
On top of this patch series, I have lined up a few more:
1. Implement a scalar diagnose command.
2. Use the built-in FSMonitor (that patch series obviously needs to wait for FSMonitor to be integrated).
3. Modify the config machinery to be more generous about concurrent writes, say, to the user-wide config.
4. A few patches to optionally build and install scalar as part of a regular Git install (also teaching git help scalar to find the Scalar documentation
These are included in my vfs-with-scalar branch thicket
[https://github.com/dscho/git/commits/vfs-with-scalar]. On top of that, this branch thicket also includes patches I do not plan on upstreaming, mainly because they are too specific either to VFS for Git, or they support Azure Repos (which does not offer partial clones but speaks the GVFS protocol, which can be used to emulate partial clones).
One other thing is very interesting about that vfs-with-scalar branch
thicket: it contains a GitHub workflow which will run Scalar's quite extensive Functional Tests suite. This test suite is quite comprehensive and caught us a lot of bugs in the past, not only in the Scalar code, but also core Git.
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 core Git, even a built-in? I wanted to provide an easy way for Git contributors to "play with" Scalar, without forcing a new top-level command into Git.
* Why implement the Scalar command in the Git code base? Apart from simplifying Scalar maintenance in the Microsoft port of Git, the tight version coupling between Git and Scalar reduces the maintenance burden even further. Besides, I believe that it will make it much easier to shift functionality from Scalar into core Git, once we took the hurdle of accepting the Scalar code into the code base.
* Why contribute Scalar to the Git project? We are biased, of course, yet our data-driven approach provides evidence that Scalar helps handling 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 Microsoft's fork of Git. We also hope that a lot of Scalar (maybe all of it) will end up in core Git, to benefit even more users.
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 (12):
scalar: add a README with a roadmap
scalar: create a rudimentary executable
scalar: start documenting the command
scalar: create test infrastructure
cmake: optionally build scalar, too
ci: also run the scalar tests
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
Matthew John Cheetham (1):
scalar: implement the delete command
.github/workflows/main.yml | 15 + Makefile | 9 + ci/run-build-and-tests.sh | 1 + ci/run-test-slice.sh | 5 + contrib/buildsystems/CMakeLists.txt | 14 + contrib/scalar/.gitignore | 2 + contrib/scalar/Makefile | 45 ++ contrib/scalar/README.md | 71 +++ contrib/scalar/scalar.c | 826 ++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 145 +++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 +++ 12 files changed, 1299 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/README.md 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: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1005%2Fdscho%2Fscalar-the-beginning-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v6:
-: ----------- > 1: 3aa095dc824 scalar: add a README with a roadmap 1: 9b0b00438ec = 2: e0693cc713c scalar: create a rudimentary executable 2: 40fee75968e ! 3: d80627615f8 scalar: start documenting the command
@@ Metadata
## Commit message ##
scalar: start documenting the command
- This commit establishes the infrastructure to build the manual page for
- the scalar command.
+ Let's build up the documentation for the Scalar command along with the
+ patches that implement its functionality.
- Signed-off-by: Johannes Schindelin <[email protected]>
-
- ## contrib/scalar/.gitignore ##
-@@
-+/*.xml
-+/*.1
-+/*.html
- /*.exe
- /scalar
+ Note: To discourage the feature-incomplete documentation from being
+ mistaken for the complete thing, we do not yet provide any way to build
+ HTML or manual pages from the text file.
- ## contrib/scalar/Makefile ##
-@@ contrib/scalar/Makefile: ifndef V
- QUIET_SUBDIR0 = +@subdir=
- QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
- $(MAKE) $(PRINT_DIR) -C $$subdir
-+ QUIET = @
- else
- export V
- endif
-@@ contrib/scalar/Makefile: $(TARGETS): $(GITLIBS) scalar.c
-
- clean:
- $(RM) $(TARGETS)
-+ $(RM) scalar.1 scalar.html scalar.xml
-
--.PHONY: $(GITLIBS) 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: $(GITLIBS) all clean docs FORCE
+ Signed-off-by: Johannes Schindelin <[email protected]>
## contrib/scalar/scalar.txt (new) ##
@@
3: e3507c2d5f7 ! 4: 9da1616849e scalar: create test infrastructure
@@ Commit message
more tests in subsequent commits that introduce corresponding, new functionality.
- Note: this test script is intended to test scalar only lightly, even
+ 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.
+ a real remote repository, and is run as part of the regular CI and PR
+ builds in that fork.
+
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: $(TARGETS): $(GITLIBS) scalar.c
clean:
- $(RM) $(TARGETS)
+ $(RM) $(TARGETS) ../../bin-wrappers/scalar
- $(RM) scalar.1 scalar.html scalar.xml
+-.PHONY: $(GITLIBS) all clean FORCE
+../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile
+ @mkdir -p ../../bin-wrappers
+ $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
@@ contrib/scalar/Makefile: $(TARGETS): $(GITLIBS) scalar.c
+test: all
+ $(MAKE) -C t
+
- docs: scalar.html scalar.1
-
- scalar.html: | scalar.1 # prevent them from trying to build doc.dep in parallel -@@ contrib/scalar/Makefile: scalar.html scalar.1: scalar.txt
- ../contrib/scalar/$@
- $(QUIET)test scalar.1 != "$@" || mv ../../Documentation/$@ .
-
--.PHONY: $(GITLIBS) all clean docs FORCE
-+.PHONY: $(GITLIBS) all clean docs test FORCE
++.PHONY: $(GITLIBS) all clean test FORCE
## contrib/scalar/t/Makefile (new) ##
@@
-: ----------- > 5: dbaad4753c1 cmake: optionally build `scalar`, too
-: ----------- > 6: 1b0328fa236 ci: also run the `scalar` tests
4: 53224e506ea ! 7: cca604ef326 scalar: 'register' sets recommended config and starts maintenance
@@ contrib/scalar/scalar.c
+ { "commitGraph.generationVersion", "1" },
+ { "core.autoCRLF", "false" },
+ { "core.safeCRLF", "false" },
++ { "fetch.showForcedUpdates", "false" },
+ { NULL, NULL },
+ };
+ int i;
5: 3591e53700b = 8: 9fea89cd161 scalar: 'unregister' stops background maintenance
6: fe04ae7ec66 = 9: 5e077bf892b scalar: let 'unregister' handle a deleted enlistment directory gracefully
7: 136aec439fd = 10: dfa0c470989 scalar: implement 'scalar list'
8: ed24a5e9b4c ! 11: febefe39886 scalar: implement the `clone` subcommand @@ contrib/scalar/t/t9099-scalar.sh: PATH=$PWD/..:$PATH
. ../../../t/test-lib.sh
-+GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt" ++GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab ../cron.txt,launchctl:true,schtasks:true"
+export GIT_TEST_MAINT_SCHEDULER
+
test_expect_success 'scalar shows a usage' '
9: 5ee54a9e25f = 12: 2677bcff335 scalar: teach 'clone' to support the --single-branch option 10: 3361eb8f091 = 13: 99affb84284 scalar: implement the `run` command 11: c4778c1dc5f = 14: 69e2242240b scalar: allow reconfiguring an existing enlistment 12: cd824e9e483 = 15: 0068c18aa62 scalar: teach 'reconfigure' to optionally handle all registered enlistments 13: 843026d5481 = 16: d5218523a38 scalar: implement the `delete` command 14: 5ca169b3f3a = 17: 96a803416b5 scalar: implement the `version` command 15: e3a6eea0534 < -: ----------- scalar: accept -C and -c options before the subcommand
Signature · 1 line
gitgitgadget
PATCH v7 1/17 scalar: add a README with a roadmap
From: Johannes Schindelin <[email protected]>
The Scalar command will be contributed incrementally, over a bunch of patch series. Let's document what Scalar is about, and then describe the patch series that are planned.
--- contrib/scalar/README.md | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 contrib/scalar/README.md
contrib/scalar/README.md new file +71 -0
@@ -0,0 +1,71 @@ +# Scalar - an opinionated repository management tool + +Scalar is an add-on to Git, helping Git scale to very large repositories and +worktrees. Originally implemented in C# using .NET Core, based on the learnings +from the VFS for Git project, most of the techniques developed by the Scalar +project have been integrated into core Git already: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +This directory contains the remaining parts of Scalar that are not (yet) in +core Git. + +## Roadmap + +The idea is to populate this directory via incremental patch series and +eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The +current plan involves the following patch series: + +- `scalar-the-beginning`: The initial patch series which sets up + `contrib/scalar/` and populates it with a minimal `scalar` command that + demonstrates the fundamental ideas. + +- `scalar-c-and-C`: The `scalar` command learns about two options that can be + specified before the command, `-c <key>=<value>` and `-C <directory>`. + +- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. + +- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar + init` and in `scalar clone`, for an enormous performance boost when working + in large worktrees. This patch series necessarily depends on Jeff Hostetler's + FSMonitor patch series to be integrated into Git. + +- `scalar-gentler-config-locking`: Scalar enlistments are registered in the + user's Git config. This usually does not represent any problem because it is + rare for a user to register an enlistment. However, in Scalar's functional + tests, Scalar enlistments are created galore, and in parallel, which can lead + to lock contention. This patch series works around that problem by re-trying + to lock the config file in a gentle fashion. + +- `scalar-extra-docs`: Add some extensive documentation that has been written + in the original Scalar project (all subject to discussion, of course). + +- `optionally-install-scalar`: Now that Scalar is feature (and documentation) + complete and is verified in CI builds, let's offer to install it. + +- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to + `gitk-git/` and to `git-gui/`, making it a top-level command. + +The following two patch series exist, but there is no plan to integrate them +into Git's source tree: + +- `scalar-with-gvfs`: The primary purpose of this patch series is to support + existing Scalar users whose repositories are hosted in Azure Repos (which + does not support Git's partial clones, but supports its predecessor, the GVFS + protocol, which is used by Scalar to emulate the partial clone). + + Since the GVFS protocol will never be supported by core Git, this patch + series will remain in Microsoft's fork of Git. + +- `run-scalar-functional-tests`: The Scalar project developed a quite + comprehensive set of integration tests (or, "Functional Tests"). They are the + sole remaining part of the original C#-based Scalar project, and this patch + adds a GitHub workflow that runs them all. + + Since the tests partially depend on features that are only provided in the + `scalar-with-gvfs` patch series, this patch cannot be upstreamed.
Signature · 1 line
gitgitgadget
Re: [PATCH v7 01/17] scalar: add a README with a roadmap
Quoted 18 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The Scalar command will be contributed incrementally, over a bunch of patch series. Let's document what Scalar is about, and then describe the patch series that are planned.
Signed-off-by Johannes Schindelin <[email protected]>--- contrib/scalar/README.md | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 contrib/scalar/README.mdcontrib/scalar/README.md new file +0
-0
contrib/scalar/README.md line 4
Quoted 4 lines
contrib/scalar/README.md new file +4
-0@@ -0,0 +1,71 @@ +# Scalar - an opinionated repository management tool + +Scalar is an add-on to Git, helping Git scale to very large repositories and +worktrees.
I would rephrase this as "Scalar is an add-on to Git that helps users take advantage of advanced performance features in Git."
Git scales just fine, only it helps to enable some features that are off by default.
Quoted 23 lines
Originally implemented in C# using .NET Core, based on the learnings
contrib/scalar/README.md new file +22
-0+from the VFS for Git project, most of the techniques developed by the Scalar +project have been integrated into core Git already: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +This directory contains the remaining parts of Scalar that are not (yet) in +core Git. + +## Roadmap + +The idea is to populate this directory via incremental patch series and +eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The +current plan involves the following patch series: + +- `scalar-the-beginning`: The initial patch series which sets up + `contrib/scalar/` and populates it with a minimal `scalar` command that + demonstrates the fundamental ideas.
contrib/scalar/README.md line 36
Quoted 10 lines
contrib/scalar/README.md new file +10
-0+ +- `scalar-c-and-C`: The `scalar` command learns about two options that can be + specified before the command, `-c <key>=<value>` and `-C <directory>`. + +- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. + +- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar + init` and in `scalar clone`, for an enormous performance boost when working + in large worktrees. This patch series necessarily depends on Jeff Hostetler's + FSMonitor patch series to be integrated into Git.
You say 'scalar init' but do you mean 'scalar register'?
Quoted 5 lines
+-
scalar-gentler-config-locking: Scalar enlistments are registered in thecontrib/scalar/README.md new file +4
-0+ user's Git config. This usually does not represent any problem because it is + rare for a user to register an enlistment. However, in Scalar's functional + tests, Scalar enlistments are created galore, and in parallel, which can lead + to lock contention. This patch series works around that problem by re-trying
contrib/scalar/README.md line 52
Quoted 10 lines
contrib/scalar/README.md new file +10
-0+ to lock the config file in a gentle fashion. + +- `scalar-extra-docs`: Add some extensive documentation that has been written + in the original Scalar project (all subject to discussion, of course). + +- `optionally-install-scalar`: Now that Scalar is feature (and documentation) + complete and is verified in CI builds, let's offer to install it. + +- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to + `gitk-git/` and to `git-gui/`, making it a top-level command.
This final one is where we can make the final call about where Scalar should exist in the tree and how optional it should be. This would also move the Scalar man pages into Documentation/, along with possibly the docs from 'scalar-extra-docs', and the tests into t/. The benefit of leaving this until the end is that we can see the entirety of Scalar before making a final call.
Quoted 8 lines
contrib/scalar/README.md new file +8
-0+The following two patch series exist, but there is no plan to integrate them +into Git's source tree: + +- `scalar-with-gvfs`: The primary purpose of this patch series is to support + existing Scalar users whose repositories are hosted in Azure Repos (which + does not support Git's partial clones, but supports its predecessor, the GVFS + protocol, which is used by Scalar to emulate the partial clone). +
contrib/scalar/README.md line 71
Quoted 10 lines
contrib/scalar/README.md new file +10
-0+ Since the GVFS protocol will never be supported by core Git, this patch + series will remain in Microsoft's fork of Git. + +- `run-scalar-functional-tests`: The Scalar project developed a quite + comprehensive set of integration tests (or, "Functional Tests"). They are the + sole remaining part of the original C#-based Scalar project, and this patch + adds a GitHub workflow that runs them all. + + Since the tests partially depend on features that are only provided in the + `scalar-with-gvfs` patch series, this patch cannot be upstreamed.
These topics (in some form or another) exist on microsoft/git and are available via GPL, so we don't intend to say "we are withholding these patches" but instead are saying "We don't think the Git community is interested in these patches." There are some interesting ideas there, but the implementation is too specific to Azure Repos to be of much help in general. These still exist mainly because the GVFS protocol is what Azure Repos has instead of partial clone. We are focused instead on improving partial clone.
Thanks,
-Stolee
Re: [PATCH v7 01/17] scalar: add a README with a roadmap
Hi Stolee,
Quoted 13 lines · Derrick Stolee
Quoted 12 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The Scalar command will be contributed incrementally, over a bunch of patch series. Let's document what Scalar is about, and then describe the patch series that are planned.
Signed-off-by Johannes Schindelin <[email protected]>--- contrib/scalar/README.md | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 contrib/scalar/README.md
Quoted 13 lines
contrib/scalar/README.md line 4
Quoted 10 lines
contrib/scalar/README.md new file +4
-0@@ -0,0 +1,71 @@ +# Scalar - an opinionated repository management tool + +Scalar is an add-on to Git, helping Git scale to very large repositories and +worktrees.I would rephrase this as "Scalar is an add-on to Git that helps users take advantage of advanced performance features in Git."
Good idea!
Quoted 30 lines
Git scales just fine, only it helps to enable some features that are off by default.
Quoted 27 lines
Originally implemented in C# using .NET Core, based on the learnings
contrib/scalar/README.md new file +26
-0+from the VFS for Git project, most of the techniques developed by the Scalar +project have been integrated into core Git already: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +This directory contains the remaining parts of Scalar that are not (yet) in +core Git. + +## Roadmap + +The idea is to populate this directory via incremental patch series and +eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The +current plan involves the following patch series: + +- `scalar-the-beginning`: The initial patch series which sets up + `contrib/scalar/` and populates it with a minimal `scalar` command that + demonstrates the fundamental ideas. + +- `scalar-c-and-C`: The `scalar` command learns about two options that can be + specified before the command, `-c <key>=<value>` and `-C <directory>`. +
Quoted 8 lines
contrib/scalar/README.md line 36
Quoted 6 lines
+-
scalar-diagnose: Thescalarcommand is taught thediagnosesubcommand.contrib/scalar/README.md new file +5
-0+ +- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar + init` and in `scalar clone`, for an enormous performance boost when working + in large worktrees. This patch series necessarily depends on Jeff Hostetler's + FSMonitor patch series to be integrated into Git.You say 'scalar init' but do you mean 'scalar register'?
D'oh. Yes, I meant scalar register. I was thinking of git init too much when I wrote that.
Quoted 13 lines
Quoted 13 lines
+-
scalar-gentler-config-locking: Scalar enlistments are registered in thecontrib/scalar/README.md new file +12
-0+ user's Git config. This usually does not represent any problem because it is + rare for a user to register an enlistment. However, in Scalar's functional + tests, Scalar enlistments are created galore, and in parallel, which can lead + to lock contention. This patch series works around that problem by re-trying + to lock the config file in a gentle fashion. + +- `scalar-extra-docs`: Add some extensive documentation that has been written + in the original Scalar project (all subject to discussion, of course). + +- `optionally-install-scalar`: Now that Scalar is feature (and documentation) + complete and is verified in CI builds, let's offer to install it. +
Quoted 8 lines
Quoted 2 lines
+- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to + `gitk-git/` and to `git-gui/`, making it a top-level command.This final one is where we can make the final call about where Scalar should exist in the tree and how optional it should be. This would also move the Scalar man pages into
Documentation/, along with possibly the docs from 'scalar-extra-docs', and the tests into t/. The benefit of leaving this until the end is that we can see the entirety of Scalar before making a final call.
Yes.
It allows the current patch series to focus on the core functionality of Scalar. The move-scalar-to-toplevel patch series will present an excellent opportunity to discuss the merits and complications of accepting Scalar as a top-level command into Git, without having to delay the current patch series any further.
Quoted 19 lines
Quoted 8 lines
contrib/scalar/README.md new file +8
-0+The following two patch series exist, but there is no plan to integrate them +into Git's source tree: + +- `scalar-with-gvfs`: The primary purpose of this patch series is to support + existing Scalar users whose repositories are hosted in Azure Repos (which + does not support Git's partial clones, but supports its predecessor, the GVFS + protocol, which is used by Scalar to emulate the partial clone). +contrib/scalar/README.md line 71
Quoted 10 lines
contrib/scalar/README.md new file +10
-0+ Since the GVFS protocol will never be supported by core Git, this patch + series will remain in Microsoft's fork of Git. + +- `run-scalar-functional-tests`: The Scalar project developed a quite + comprehensive set of integration tests (or, "Functional Tests"). They are the + sole remaining part of the original C#-based Scalar project, and this patch + adds a GitHub workflow that runs them all. + + Since the tests partially depend on features that are only provided in the + `scalar-with-gvfs` patch series, this patch cannot be upstreamed.
Quoted 7 lines
These topics (in some form or another) exist on microsoft/git and are available via GPL, so we don't intend to say "we are withholding these patches" but instead are saying "We don't think the Git community is interested in these patches." There are some interesting ideas there, but the implementation is too specific to Azure Repos to be of much help in general. These still exist mainly because the GVFS protocol is what Azure Repos has instead of partial clone. We are focused instead on improving partial clone.
Good point. What I wrote does not fully reflect that. I have changed it.
Do you see anything in the remainder of the patch series that still needs to be improved?
Thanks,
Dscho
PATCH v7 2/17 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/
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 | 9 +++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +9 -0
@@ -2456,6 +2456,11 @@ OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif + +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + .PHONY: objects objects: $(OBJECTS)
@@ -2589,6 +2594,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: $(GITLIBS) 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 v7 3/17 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Let's build up the documentation for the Scalar command along with the patches that implement its functionality.
Note: To discourage the feature-incomplete documentation from being mistaken for the complete thing, we do not yet provide any way to build HTML or manual pages from the text file.
--- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 contrib/scalar/scalar.txt
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 v7 4/17 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 and PR builds in that fork.
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
@@ -20,7 +21,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 ../../,,$@)
@@ -29,6 +30,16 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar -.PHONY: $(GITLIBS) all clean FORCE +../../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 + +.PHONY: $(GITLIBS) all clean 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 v7 5/17 cmake: optionally build `scalar`, too
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The CMake configuration unfortunately does not let us encapsulate all (or at least the vast majority) of Scalar's build definition in the contrib/scalar/ subdirectory.
To alleviate that somewhat, we guard the inclusion of Scalar via the INCLUDE_SCALAR environment variable.
--- contrib/buildsystems/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
contrib/buildsystems/CMakeLists.txt +14 -0
@@ -729,6 +729,13 @@ if(CURL_FOUND) endif() endif() +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c) + target_link_libraries(scalar common-main) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/contrib/scalar) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/contrib/scalar) +endif() + parse_makefile_for_executables(git_builtin_extra "BUILT_INS") option(SKIP_DASHED_BUILT_INS "Skip hardlinking the dashed versions of the built-ins")
@@ -953,6 +960,13 @@ string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}") string(REPLACE "@@PROG@@" "git-cvsserver" content "${content}") file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/git-cvsserver ${content}) +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + file(STRINGS ${CMAKE_SOURCE_DIR}/wrap-for-bin.sh content NEWLINE_CONSUME) + string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}") + string(REPLACE "@@PROG@@" "contrib/scalar/scalar${EXE_EXTENSION}" content "${content}") + file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/scalar ${content}) +endif() + #options for configuring test options option(PERL_TESTS "Perform tests that use perl" ON) option(PYTHON_TESTS "Perform tests that use python" ON)
Signature · 1 line
gitgitgadget
Re: [PATCH v7 05/17] cmake: optionally build `scalar`, too
Quoted 6 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The CMake configuration unfortunately does not let us encapsulate all (or at least the vast majority) of Scalar's build definition in the
contrib/scalar/subdirectory.
I believe that this isn't fully correct in that you could call add_subdirectory() with
a directory that isn't a subdirectory so long as you provide it an explicit binary directory
which is part of what we're doing here anyways. (at least it works fine for me with
cmake version 3.21)
Quoted 14 lines
To alleviate that somewhat, we guard the inclusion of Scalar via the
INCLUDE_SCALARenvironment variable.Signed-off-by Johannes Schindelin <[email protected]>--- contrib/buildsystems/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+)contrib/buildsystems/CMakeLists.txt +0
-0
contrib/buildsystems/CMakeLists.txt if(CURL_FOUND) · line 737
Quoted 9 lines
contrib/buildsystems/CMakeLists.txt +6
-0@@ -729,6 +729,13 @@ if(CURL_FOUND) endif() endif() +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c) + target_link_libraries(scalar common-main) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/contrib/scalar) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/contrib/scalar) +endif()
Would it make sense to just mark this with EXCLUDE_FROM_ALL so that way you don't have to have
the INCLUDE_SCALAR environment variable just for this? This way people who don't want
to build scalar can still run cmake --build . and not have scalar built and people who do want
it built can just run cmake --build . --target scalar, alternatively you could also do something like
if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "")
add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c)else()
add_executable(scalar EXCLUDE_FROM_ALL${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c)endif()
Just some food for thought,
Matthew Rogers
Re: [PATCH v7 05/17] cmake: optionally build `scalar`, too
Hi Matt,
Quoted 9 lines · Matt Rogers
Quoted 6 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
The CMake configuration unfortunately does not let us encapsulate all (or at least the vast majority) of Scalar's build definition in the
contrib/scalar/subdirectory.
Quoted 5 lines
I believe that this isn't fully correct in that you could call
add_subdirectory()with a directory that isn't a subdirectory so long as you provide it an explicit binary directory which is part of what we're doing here anyways. (at least it works fine for me with cmake version 3.21)
You are of course correct, you _could_ put things into a subdirectory with CMake. However, please note that scalar wants to link to libgit.a (and also link in common-main.o), therefore the subdirectory _still_ has to have some sort of connection to the top-level configuration.
I shall clarify that in the commit message in the next iteration.
Quoted 30 lines
Quoted 13 lines
To alleviate that somewhat, we guard the inclusion of Scalar via the
INCLUDE_SCALARenvironment variable.Signed-off-by Johannes Schindelin <[email protected]>--- contrib/buildsystems/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+)contrib/buildsystems/CMakeLists.txt +0
-0contrib/buildsystems/CMakeLists.txt if(CURL_FOUND) · line 737
Quoted 9 lines
contrib/buildsystems/CMakeLists.txt +6
-0@@ -729,6 +729,13 @@ if(CURL_FOUND) endif() endif() +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c) + target_link_libraries(scalar common-main) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/contrib/scalar) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/contrib/scalar) +endif()Would it make sense to just mark this with
EXCLUDE_FROM_ALLso that way you don't have to have theINCLUDE_SCALARenvironment variable just for this? This way people who don't want to build scalar can still runcmake --build .and not have scalar built and people who do want it built can just runcmake --build . --target scalar, alternatively you could also do something like
Quoted 6 lines
if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "")add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c)
else()
add_executable(scalarEXCLUDE_FROM_ALL${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c)endif()
The entire point of pulling this patch from a much later patch series to the first Scalar patch series is to allow for easy testing in the CI build. To that end, what you propose would complexify the setup too much for my liking.
Since the end game seems to be for Scalar to become a top-level command (at least that's how I read Junio's favorable reply at
https://lore.kernel.org/git/[email protected]/), I suspect that it might not matter whether we stay with INCLUDE_SCALAR or change it to an idempotent alternative, anyway.
Ciao,
Dscho
PATCH v7 6/17 ci: also run the `scalar` tests
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Since Scalar depends on libgit.a, it makes sense to ensure in the CI and the PR builds that it does not get broken in case of industrious refactorings of the core Git code.
--- .github/workflows/main.yml | 15 +++++++++++++++ ci/run-build-and-tests.sh | 1 + ci/run-test-slice.sh | 5 +++++ 3 files changed, 21 insertions(+)
.github/workflows/main.yml +15 -0
@@ -89,6 +89,13 @@ jobs: HOME: ${{runner.workspace}} NO_PERL: 1 run: . /etc/profile && ci/make-test-artifacts.sh artifacts + - name: build Scalar + shell: bash + run: | + make -C contrib/scalar && + mkdir -p artifacts/bin-wrappers artifacts/contrib/scalar && + cp contrib/scalar/scalar.exe artifacts/contrib/scalar/ && + cp bin-wrappers/scalar artifacts/bin-wrappers/ - name: zip up tracked files run: git archive -o artifacts/tracked.tar.gz HEAD - name: upload tracked files and build artifacts
@@ -157,6 +164,8 @@ jobs: run: compat\vcbuild\vcpkg_copy_dlls.bat release - name: generate Visual Studio solution shell: bash + env: + INCLUDE_SCALAR: YesPlease run: | cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \ -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON
@@ -170,6 +179,12 @@ jobs: run: | mkdir -p artifacts && eval "$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts NO_GETTEXT=YesPlease 2>&1 | grep ^tar)" + - name: copy Scalar + shell: bash + run: | + mkdir -p artifacts/bin-wrappers artifacts/contrib/scalar && + cp contrib/scalar/scalar.exe artifacts/contrib/scalar/ && + cp bin-wrappers/scalar artifacts/bin-wrappers/ - name: zip up tracked files run: git archive -o artifacts/tracked.tar.gz HEAD - name: upload tracked files and build artifacts
ci/run-build-and-tests.sh +1 -0
@@ -49,6 +49,7 @@ linux-gcc-4.8|pedantic) make test ;; esac +make -C contrib/scalar test check_unignored_build_artifacts
ci/run-test-slice.sh +5 -0
@@ -14,4 +14,9 @@ make --quiet -C t T="$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | tr '\n' ' ')" +if test 0 = "$1" +then + make -C contrib/scalar test +fi + check_unignored_build_artifacts
Signature · 1 line
gitgitgadget
PATCH v7 7/17 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 | 249 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 266 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +249 -0
@@ -5,11 +5,260 @@#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" ); +if (is_nonbare_repository_dir (&path)) { +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 */ +if (is_nonbare_repository_dir (&path)) { +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 ; + } + }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" }, + {"fetch.showForcedUpdates" ,"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 v7 8/17 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
@@ -198,12 +198,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;
@@ -214,24 +214,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; }
@@ -254,11 +269,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 v7 9/17 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
@@ -269,6 +269,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[] = {
@@ -282,6 +300,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 v7 10/17 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
@@ -251,6 +251,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[] = {
@@ -337,6 +347,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 v7 11/17 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 | 32 ++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 262 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
@@ -251,6 +252,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 )
@@ -347,6 +547,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 +29 -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,37 @@ 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[:<directory>]`. + +-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 +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,launchctl:true,schtasks:true" +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 v7 12/17 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
@@ -327,12 +327,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[] = {
@@ -403,7 +406,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>]
@@ -57,6 +57,16 @@ HEAD[:<directory>]`. 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 v7 13/17 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
@@ -484,6 +484,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 ;
@@ -556,6 +619,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 v7 14/17 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
@@ -108,18 +108,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
@@ -133,28 +135,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" },
@@ -166,7 +169,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 )
@@ -231,7 +235,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 );
@@ -419,7 +423,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 ))) {
@@ -484,6 +488,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[] = {
@@ -620,6 +642,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 v7 15/17 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
@@ -488,22 +488,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 --------
@@ -125,6 +125,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 v7 16/17 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 | 63 ++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 8 ++++ contrib/scalar/t/t9099-scalar.sh | 9 +++++ 3 files changed, 80 insertions(+)
contrib/scalar/scalar.c +63 -0
@@ -8,6 +8,8 @@#include "config.h" #include "run-command.h" #include "refs.h" +#include "dir.h" +#include "packfile.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -328,6 +330,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 ;
@@ -688,6 +717,39 @@ static int cmd_unregister(int argc, const char **argv)return unregister_dir (); } +static int cmd_delete (int argc,const char **argv) +{ +char *cwd =xgetcwd (); +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); + +if (dir_inside_of (cwd, enlistment.buf) >=0 ) + res =error (_ ("refusing to delete current working directory" )); +else { +close_object_store (the_repository->objects); + res =delete_enlistment (&enlistment); + } +strbuf_release (&enlistment); +free (cwd); + +return res; +} + static struct { const char *name; int (*fn)(int, const char **);
@@ -698,6 +760,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 -----------
@@ -128,6 +129,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 v7 17/17 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
@@ -10,6 +10,7 @@#include "refs.h" #include "dir.h" #include "packfile.h" +#include "help.h" /* * Remove the deepest subdirectory in the provided path string. Path must not
@@ -357,6 +358,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 ;
@@ -750,6 +760,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 **);
@@ -761,6 +799,7 @@ static struct { { "run", cmd_run }, { "reconfigure", cmd_reconfigure }, { "delete", cmd_delete }, + { "version", cmd_version }, { NULL, NULL}, };
Signature · 1 line
gitgitgadget
Quoted 8 lines · Johannes Schindelin via GitGitGadget
* To avoid mistaking the current patch series for being feature-complete enough to unleash onto end users, I moved the
Makefilerules to build HTML/manual pages to a later patch series.
[...]
* I added two patches that I had planned on keeping in an add-on patch series for later, to build and test Scalar as part of the CI. I am still not 100% certain that it is a good idea to do so already now, but let's see what the reviewers have to say.
I don't expect given [1] that I'll get a reply to this, but as noted there I've had outstanding reviews of this series (including proposed patches) for weeks/months now that haven't been addressed, including the patch-on-top I suggested in the previous round of discussion [2].
Looking at the new README.md it seems that the change of plan between v6..v7 is that after insisting that this command must stay decoupled in contrib/scalar/ and have its own build/test etc. infrastructure, the end-goal now eventual full integration at the top-level.
Peeking a branch of yours[3] there's a change[4] in it that's much like my [2], some of the hunks are even the same.
I'm quite happy with that end-state, but still don't understand why the "scalar-extra-docs" and "run-scalar-functional-tests" (in your README.md here) couldn't be squashed into this first step, as suggested in my [2].
The end-state is the same, but doing it that way avoids all this path-based churn, setting up build systems that are then going away in the short-to-medium term etc, as well as the new custom just-for-scalar CI integration in this v7, you'd get that and more for free with my [2].
All of that is apparently to:
Quoted 2 lines
[...]avoid mistaking the current patch series for being feature-complete enough to unleash onto end users[. ...]
Which is fair enough, but doesn't explain why this back-and-forth churn is needed to reach an end-goal we apparently now almost entirely agree on.
I.e. in my [2] it's built & tested by default, but not installed. So end users aren't seeing it. The small bit of proposed in-advanced reference to it in our documentation is there in [2], but that's trivially tweaked, we could defer that too. No end-user would see any of it.
So who's being helped by these intermediate steps?
As far as I can tell based on your previous comments the perceived necessity these intermediate steps comes from a narrow reading of contrib/README,
i.e. that software in this sort of flux *MUST* live in contrib, even if we've got plans & patches in-progress to move it out eventually.
Whereas I think we can just document that it's in flux, and skip the intermediate churn to get to the agreed-upon end-state.
As before I'm happy to engage with you, but I must say at this point I'm not getting my hopes up much.
Cheers.
1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/
3. https://github.com/dscho/git/tree/vfs-with-scalar
4. https://github.com/dscho/git/commit/f9b8e5d5e7e
v8 cover Upstreaming the Scalar command
In reply to Johannes Schindelin via GitGitGadgettl;dr: This series contributes the core part of the Scalar command to the Git project. This command provides an opinionated way to create and configure Git repositories with a focus on very large repositories.
Changes since v7:
* Clarified in the commit message why we cannot easily encapsulate the Scalar part of the CMake configuration in contrib/scalar/.
* Improved the README.md.
Changes since v6:
* Rebased on top of v2.34.0.
* Inserted a commit that adds contrib/scalar/README.md, containing the roadmap of what I have planned for Scalar.
* The Scalar test's definition of GIT_TEST_MAINT_SCHEDULER has been adjusted to accommodate for a change in v2.32.0..v2.34.0.
* The config setting defaults now include fetch.showForcedUpdates=false, which has been identified as helping with a performance issue in large repositories.
* To avoid mistaking the current patch series for being feature-complete enough to unleash onto end users, I moved the Makefile rules to build HTML/manual pages to a later patch series.
* The patch that adds support for -c <key>=<value> and -C <directory> was moved to its own add-on patch series: While it is obvious that those options are valuable to have, an open question is whether there are other "pre-command" options in git that would be useful, too, and I would like to postpone that discussion to that date.
* I added two patches that I had planned on keeping in an add-on patch series for later, to build and test Scalar as part of the CI. I am still not 100% certain that it is a good idea to do so already now, but let's see what the reviewers have to say.
Changes since v5:
* Fixed the commit message talking about make -C contrib/scalar/Makefile.
* Fixed the git ls-tree invocation suggested in the manual for scalar clone.
* Invoking make -C contrib/scalar, then changing a source file of libgit.a and then immediately invoking make -C contrib/scalar again will now implicitly rebuild libgit.a.
Changes since v4:
* scalar delete now refuses to delete anything if it was started from within the enlistment.
* scalar delete releases any handles to the object store before deleting the enlistment.
* The OBJECTS list in the Makefile will now include Scalar.
* scalar register now supports secondary worktrees, in addition to the primary worktree.
Changes since v3:
* Moved the "Changes since" section to the top, to make it easier to see what changed.
* Reworded the commit message of the first patch.
* Removed the [RFC] prefix because I did not hear any objections against putting this into contrib/.
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
Background
==========
Microsoft invested a lot of effort into scaling Git to the needs of the Windows operating system source code. Based on the experience of the first approach, VFS for Git, the Scalar project was started. Scalar specifically has as its core goal to funnel all improvements into core Git.
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 scheduled 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/ (it offers a Git for Windows installer, a macOS package and an Ubuntu package, Scalar has been included since v2.33.0.vfs.0.0).
Next steps
==========
Since there are existing Scalar users, I want to ensure backwards-compatibility with its existing command-line interface. Keeping that in mind, everything in this series is up for discussion.
I obviously believe that Scalar brings a huge benefit, and think that it would be ideal for all of Scalar's learnings to end up in git clone/git init/git maintenance eventually. It is also conceivable, however, that the scalar command could graduate to be a core part of Git at some stage in the future (such a decision would probably depend highly on users' feedback). See also the discussion about the architecture of Scalar
[https://lore.kernel.org/git/[email protected]/],
kicked off by Stolee.
On top of this patch series, I have lined up a few more:
1. Implement a scalar diagnose command.
2. Use the built-in FSMonitor (that patch series obviously needs to wait for FSMonitor to be integrated).
3. Modify the config machinery to be more generous about concurrent writes, say, to the user-wide config.
4. A few patches to optionally build and install scalar as part of a regular Git install (also teaching git help scalar to find the Scalar documentation
These are included in my vfs-with-scalar branch thicket
[https://github.com/dscho/git/commits/vfs-with-scalar]. On top of that, this branch thicket also includes patches I do not plan on upstreaming, mainly because they are too specific either to VFS for Git, or they support Azure Repos (which does not offer partial clones but speaks the GVFS protocol, which can be used to emulate partial clones).
One other thing is very interesting about that vfs-with-scalar branch
thicket: it contains a GitHub workflow which will run Scalar's quite extensive Functional Tests suite. This test suite is quite comprehensive and caught us a lot of bugs in the past, not only in the Scalar code, but also core Git.
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 core Git, even a built-in? I wanted to provide an easy way for Git contributors to "play with" Scalar, without forcing a new top-level command into Git.
* Why implement the Scalar command in the Git code base? Apart from simplifying Scalar maintenance in the Microsoft port of Git, the tight version coupling between Git and Scalar reduces the maintenance burden even further. Besides, I believe that it will make it much easier to shift functionality from Scalar into core Git, once we took the hurdle of accepting the Scalar code into the code base.
* Why contribute Scalar to the Git project? We are biased, of course, yet our data-driven approach provides evidence that Scalar helps handling 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 Microsoft's fork of Git. We also hope that a lot of Scalar (maybe all of it) will end up in core Git, to benefit even more users.
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 (12):
scalar: add a README with a roadmap
scalar: create a rudimentary executable
scalar: start documenting the command
scalar: create test infrastructure
cmake: optionally build scalar, too
ci: also run the scalar tests
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
Matthew John Cheetham (1):
scalar: implement the delete command
.github/workflows/main.yml | 15 + Makefile | 9 + ci/run-build-and-tests.sh | 1 + ci/run-test-slice.sh | 5 + contrib/buildsystems/CMakeLists.txt | 14 + contrib/scalar/.gitignore | 2 + contrib/scalar/Makefile | 45 ++ contrib/scalar/README.md | 82 +++ contrib/scalar/scalar.c | 826 ++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 145 +++++ contrib/scalar/t/Makefile | 78 +++ contrib/scalar/t/t9099-scalar.sh | 88 +++ 12 files changed, 1310 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/README.md 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: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1005%2Fdscho%2Fscalar-the-beginning-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1005/dscho/scalar-the-beginning-v8
Pull-Request: https://github.com/gitgitgadget/git/pull/1005
Range-diff vs v7:
1: 3aa095dc824 ! 1: 3a2e28275f1 scalar: add a README with a roadmap
@@ contrib/scalar/README.md (new)
@@
+# Scalar - an opinionated repository management tool
+
-+Scalar is an add-on to Git, helping Git scale to very large repositories and -+worktrees. Originally implemented in C# using .NET Core, based on the learnings -+from the VFS for Git project, most of the techniques developed by the Scalar -+project have been integrated into core Git already:
++Scalar is an add-on to Git that helps users take advantage of advanced ++performance features in Git. Originally implemented in C# using .NET Core, ++based on the learnings from the VFS for Git project, most of the techniques ++developed by the Scalar project have been integrated into core Git already:
+
+* partial clone,
+* commit graphs,
@@ contrib/scalar/README.md (new)
+- scalar-diagnose: The scalar command is taught the diagnose subcommand. +
+- scalar-and-builtin-fsmonitor: The built-in FSMonitor is enabled in `scalar
-+ init and in scalar clone`, for an enormous performance boost when working -+ in large worktrees. This patch series necessarily depends on Jeff Hostetler's -+ FSMonitor patch series to be integrated into Git.
++ register and in scalar clone`, for an enormous performance boost when ++ working in large worktrees. This patch series necessarily depends on Jeff ++ Hostetler's FSMonitor patch series to be integrated into Git.
+
+- scalar-gentler-config-locking: Scalar enlistments are registered in the
+ user's Git config. This usually does not represent any problem because it is
@@ contrib/scalar/README.md (new)
+- move-scalar-to-toplevel: Now that Scalar is complete, let's move it next to
+ gitk-git/ and to git-gui/, making it a top-level command.
+
-+The following two patch series exist, but there is no plan to integrate them -+into Git's source tree:
++The following two patch series exist in Microsoft's fork of Git and are ++publicly available. There is no current plan to upstream them, not because I ++want to withhold these patches, but because I don't think the Git community is ++interested in these patches.
++
++There are some interesting ideas there, but the implementation is too specific ++to Azure Repos and/or VFS for Git to be of much help in general (and also: my ++colleagues tried to upstream some patches already and the enthusiasm for ++integrating things related to Azure Repos and VFS for Git can be summarized in ++very, very few words).
++
++These still exist mainly because the GVFS protocol is what Azure Repos has ++instead of partial clone, while Git is focused on improving partial clone:
+
+- scalar-with-gvfs: The primary purpose of this patch series is to support
+ existing Scalar users whose repositories are hosted in Azure Repos (which
2: e0693cc713c = 2: 50160d61a41 scalar: create a rudimentary executable 3: d80627615f8 = 3: 74cd6410931 scalar: start documenting the command 4: 9da1616849e = 4: 37231a4dd07 scalar: create test infrastructure 5: dbaad4753c1 ! 5: a39b9c81214 cmake: optionally build `scalar`, too
@@ Metadata
## Commit message ##
cmake: optionally build scalar, too
- The CMake configuration unfortunately does not let us encapsulate all
- (or at least the vast majority) of Scalar's build definition in the
- contrib/scalar/ subdirectory.
+ The CMake configuration unfortunately does not let us easily encapsulate
+ Scalar's build definition in the contrib/scalar/ subdirectory: The
+ scalar executable needs to link in libgit.a and common-main.o, for
+ example.
+
+ Also, scalar.c includes Git's header files, which means that
+ scalar.c needs to be compiled with the very same flags as libgit.a
+ lest scalar.o and libgit.a have different ideas of, say,
+ platform_SHA_CTX, which would naturally lead to memory corruption.
To alleviate that somewhat, we guard the inclusion of Scalar via the INCLUDE_SCALAR environment variable.
6: 1b0328fa236 = 6: 8e3542e43f7 ci: also run the `scalar` tests 7: cca604ef326 = 7: 385abdb8d8e scalar: 'register' sets recommended config and starts maintenance 8: 9fea89cd161 = 8: 64c6a75353e scalar: 'unregister' stops background maintenance 9: 5e077bf892b = 9: f7fc1958b9e scalar: let 'unregister' handle a deleted enlistment directory gracefully 10: dfa0c470989 = 10: fd2680bc945 scalar: implement 'scalar list' 11: febefe39886 = 11: 4966a43aad9 scalar: implement the `clone` subcommand 12: 2677bcff335 = 12: b00d68b37b0 scalar: teach 'clone' to support the --single-branch option 13: 99affb84284 = 13: 771d826bbb1 scalar: implement the `run` command 14: 69e2242240b = 14: a8b2d26a830 scalar: allow reconfiguring an existing enlistment 15: 0068c18aa62 = 15: ca284ff34a2 scalar: teach 'reconfigure' to optionally handle all registered enlistments 16: d5218523a38 = 16: 9983eb8912c scalar: implement the `delete` command 17: 96a803416b5 = 17: 889f613ab18 scalar: implement the `version` command
Signature · 1 line
gitgitgadget
PATCH v8 1/17 scalar: add a README with a roadmap
From: Johannes Schindelin <[email protected]>
The Scalar command will be contributed incrementally, over a bunch of patch series. Let's document what Scalar is about, and then describe the patch series that are planned.
--- contrib/scalar/README.md | 82 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 contrib/scalar/README.md
contrib/scalar/README.md new file +82 -0
@@ -0,0 +1,82 @@ +# Scalar - an opinionated repository management tool + +Scalar is an add-on to Git that helps users take advantage of advanced +performance features in Git. Originally implemented in C# using .NET Core, +based on the learnings from the VFS for Git project, most of the techniques +developed by the Scalar project have been integrated into core Git already: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +This directory contains the remaining parts of Scalar that are not (yet) in +core Git. + +## Roadmap + +The idea is to populate this directory via incremental patch series and +eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The +current plan involves the following patch series: + +- `scalar-the-beginning`: The initial patch series which sets up + `contrib/scalar/` and populates it with a minimal `scalar` command that + demonstrates the fundamental ideas. + +- `scalar-c-and-C`: The `scalar` command learns about two options that can be + specified before the command, `-c <key>=<value>` and `-C <directory>`. + +- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. + +- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar + register` and in `scalar clone`, for an enormous performance boost when + working in large worktrees. This patch series necessarily depends on Jeff + Hostetler's FSMonitor patch series to be integrated into Git. + +- `scalar-gentler-config-locking`: Scalar enlistments are registered in the + user's Git config. This usually does not represent any problem because it is + rare for a user to register an enlistment. However, in Scalar's functional + tests, Scalar enlistments are created galore, and in parallel, which can lead + to lock contention. This patch series works around that problem by re-trying + to lock the config file in a gentle fashion. + +- `scalar-extra-docs`: Add some extensive documentation that has been written + in the original Scalar project (all subject to discussion, of course). + +- `optionally-install-scalar`: Now that Scalar is feature (and documentation) + complete and is verified in CI builds, let's offer to install it. + +- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to + `gitk-git/` and to `git-gui/`, making it a top-level command. + +The following two patch series exist in Microsoft's fork of Git and are +publicly available. There is no current plan to upstream them, not because I +want to withhold these patches, but because I don't think the Git community is +interested in these patches. + +There are some interesting ideas there, but the implementation is too specific +to Azure Repos and/or VFS for Git to be of much help in general (and also: my +colleagues tried to upstream some patches already and the enthusiasm for +integrating things related to Azure Repos and VFS for Git can be summarized in +very, very few words). + +These still exist mainly because the GVFS protocol is what Azure Repos has +instead of partial clone, while Git is focused on improving partial clone: + +- `scalar-with-gvfs`: The primary purpose of this patch series is to support + existing Scalar users whose repositories are hosted in Azure Repos (which + does not support Git's partial clones, but supports its predecessor, the GVFS + protocol, which is used by Scalar to emulate the partial clone). + + Since the GVFS protocol will never be supported by core Git, this patch + series will remain in Microsoft's fork of Git. + +- `run-scalar-functional-tests`: The Scalar project developed a quite + comprehensive set of integration tests (or, "Functional Tests"). They are the + sole remaining part of the original C#-based Scalar project, and this patch + adds a GitHub workflow that runs them all. + + Since the tests partially depend on features that are only provided in the + `scalar-with-gvfs` patch series, this patch cannot be upstreamed.
Signature · 1 line
gitgitgadget
PATCH v8 2/17 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/
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 | 9 +++++++++ contrib/scalar/.gitignore | 2 ++ contrib/scalar/Makefile | 34 ++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 contrib/scalar/.gitignore create mode 100644 contrib/scalar/Makefile create mode 100644 contrib/scalar/scalar.c
Makefile +9 -0
@@ -2456,6 +2456,11 @@ OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif + +SCALAR_SOURCES := contrib/scalar/scalar.c +SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) +OBJECTS += $(SCALAR_OBJECTS) + .PHONY: objects objects: $(OBJECTS)
@@ -2589,6 +2594,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: $(GITLIBS) 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 v8 3/17 scalar: start documenting the command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Let's build up the documentation for the Scalar command along with the patches that implement its functionality.
Note: To discourage the feature-incomplete documentation from being mistaken for the complete thing, we do not yet provide any way to build HTML or manual pages from the text file.
--- contrib/scalar/scalar.txt | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 contrib/scalar/scalar.txt
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 v8 4/17 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 and PR builds in that fork.
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
@@ -20,7 +21,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 ../../,,$@)
@@ -29,6 +30,16 @@ $(TARGETS): $(GITLIBS) scalar.c $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) clean: - $(RM) $(TARGETS) + $(RM) $(TARGETS) ../../bin-wrappers/scalar -.PHONY: $(GITLIBS) all clean FORCE +../../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 + +.PHONY: $(GITLIBS) all clean 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
Re: [PATCH v8 04/17] scalar: create test infrastructure
Quoted 7 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]> [...]
+-include ../../../config.mak.autogen +-include ../../../config.makcontrib/scalar/t/Makefile new file +3
-0+ +SHELL_PATH ?= $(SHELL) +PERL_PATH ?= /usr/bin/perl
contrib/scalar/t/Makefile line 20
Quoted 10 lines
contrib/scalar/t/Makefile new file +10
-0+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
So here we'll inject our output into t/test-results[...]
Quoted 1 line
+T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
...end up with a $(T) with just the one scalar test...
contrib/scalar/t/Makefile line 54
Quoted 5 lines
contrib/scalar/t/Makefile new file +5
-0+test-lint-duplicates: + @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ + test -z "$$dups" || { \ + echo >&2 "duplicate test numbers:" $$dups; exit 1; } +
...so I *think* (but haven't checked) that print-test-failures.sh will work with the later CI integration, but both t/Makefile & this one assume in some ways that test numbers are unique, this now lives in the same namespace, but neither check the two for conflicts.
We only have the one t9099 test here, so unless we add another one in the top-level t/ that should be OK, but is there any reason to carry this at all? It seems pointless given the above.
I think to make this meaningful we'd need to teach t/Makefile to have a T_ALL variable or something, derived from its T, and have that wildcard the scalar tests and other contrib tests and check them for namespacing conflicts.
contrib/scalar/t/Makefile line 77
Quoted 3 lines
contrib/scalar/t/Makefile new file +3
-0+test-results: + mkdir -p test-results +
This target seems to be unused, don't we always use the top-level t/test-results? This seems to be copy/pasting also dead code from contrib/subtree/t/Makefile.
PATCH v8 5/17 cmake: optionally build `scalar`, too
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The CMake configuration unfortunately does not let us easily encapsulate Scalar's build definition in the contrib/scalar/ subdirectory: The scalar executable needs to link in libgit.a and common-main.o, for example.
Also, scalar.c includes Git's header files, which means that scalar.c needs to be compiled with the very same flags as libgit.a lest scalar.o and libgit.a have different ideas of, say, platform_SHA_CTX, which would naturally lead to memory corruption.
To alleviate that somewhat, we guard the inclusion of Scalar via the INCLUDE_SCALAR environment variable.
--- contrib/buildsystems/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
contrib/buildsystems/CMakeLists.txt +14 -0
@@ -729,6 +729,13 @@ if(CURL_FOUND) endif() endif() +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + add_executable(scalar ${CMAKE_SOURCE_DIR}/contrib/scalar/scalar.c) + target_link_libraries(scalar common-main) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/contrib/scalar) + set_target_properties(scalar PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/contrib/scalar) +endif() + parse_makefile_for_executables(git_builtin_extra "BUILT_INS") option(SKIP_DASHED_BUILT_INS "Skip hardlinking the dashed versions of the built-ins")
@@ -953,6 +960,13 @@ string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}") string(REPLACE "@@PROG@@" "git-cvsserver" content "${content}") file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/git-cvsserver ${content}) +if(DEFINED ENV{INCLUDE_SCALAR} AND NOT ENV{INCLUDE_SCALAR} STREQUAL "") + file(STRINGS ${CMAKE_SOURCE_DIR}/wrap-for-bin.sh content NEWLINE_CONSUME) + string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}") + string(REPLACE "@@PROG@@" "contrib/scalar/scalar${EXE_EXTENSION}" content "${content}") + file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/scalar ${content}) +endif() + #options for configuring test options option(PERL_TESTS "Perform tests that use perl" ON) option(PYTHON_TESTS "Perform tests that use python" ON)
Signature · 1 line
gitgitgadget
PATCH v8 6/17 ci: also run the `scalar` tests
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Since Scalar depends on libgit.a, it makes sense to ensure in the CI and the PR builds that it does not get broken in case of industrious refactorings of the core Git code.
--- .github/workflows/main.yml | 15 +++++++++++++++ ci/run-build-and-tests.sh | 1 + ci/run-test-slice.sh | 5 +++++ 3 files changed, 21 insertions(+)
.github/workflows/main.yml +15 -0
@@ -89,6 +89,13 @@ jobs: HOME: ${{runner.workspace}} NO_PERL: 1 run: . /etc/profile && ci/make-test-artifacts.sh artifacts + - name: build Scalar + shell: bash + run: | + make -C contrib/scalar && + mkdir -p artifacts/bin-wrappers artifacts/contrib/scalar && + cp contrib/scalar/scalar.exe artifacts/contrib/scalar/ && + cp bin-wrappers/scalar artifacts/bin-wrappers/ - name: zip up tracked files run: git archive -o artifacts/tracked.tar.gz HEAD - name: upload tracked files and build artifacts
@@ -157,6 +164,8 @@ jobs: run: compat\vcbuild\vcpkg_copy_dlls.bat release - name: generate Visual Studio solution shell: bash + env: + INCLUDE_SCALAR: YesPlease run: | cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \ -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON
@@ -170,6 +179,12 @@ jobs: run: | mkdir -p artifacts && eval "$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts NO_GETTEXT=YesPlease 2>&1 | grep ^tar)" + - name: copy Scalar + shell: bash + run: | + mkdir -p artifacts/bin-wrappers artifacts/contrib/scalar && + cp contrib/scalar/scalar.exe artifacts/contrib/scalar/ && + cp bin-wrappers/scalar artifacts/bin-wrappers/ - name: zip up tracked files run: git archive -o artifacts/tracked.tar.gz HEAD - name: upload tracked files and build artifacts
ci/run-build-and-tests.sh +1 -0
@@ -49,6 +49,7 @@ linux-gcc-4.8|pedantic) make test ;; esac +make -C contrib/scalar test check_unignored_build_artifacts
ci/run-test-slice.sh +5 -0
@@ -14,4 +14,9 @@ make --quiet -C t T="$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | tr '\n' ' ')" +if test 0 = "$1" +then + make -C contrib/scalar test +fi + check_unignored_build_artifacts
Signature · 1 line
gitgitgadget
PATCH v8 7/17 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 | 249 ++++++++++++++++++++++++++++++++++++++ contrib/scalar/scalar.txt | 18 ++- 2 files changed, 266 insertions(+), 1 deletion(-)
contrib/scalar/scalar.c +249 -0
@@ -5,11 +5,260 @@#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" ); +if (is_nonbare_repository_dir (&path)) { +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 */ +if (is_nonbare_repository_dir (&path)) { +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 ; + } + }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" }, + {"fetch.showForcedUpdates" ,"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 v8 8/17 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
@@ -198,12 +198,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;
@@ -214,24 +214,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; }
@@ -254,11 +269,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 v8 9/17 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
@@ -269,6 +269,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[] = {
@@ -282,6 +300,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 v8 10/17 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
@@ -251,6 +251,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[] = {
@@ -337,6 +347,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 v8 11/17 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 | 32 ++++- contrib/scalar/t/t9099-scalar.sh | 32 +++++ 3 files changed, 262 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
@@ -251,6 +252,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 )
@@ -347,6 +547,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 +29 -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,37 @@ 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[:<directory>]`. + +-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 +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,launchctl:true,schtasks:true" +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