oh-my-space 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/build-info.json +1 -1
- package/dist/oms.js +187 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Command name: `oms`
|
|
|
10
10
|
|
|
11
11
|
Git submodules give you three things for free: a **reproducible pin** (the parent records each source's exact commit), **visibility** (`git status` shows when a pointer moved), and **history** (the pointer travels with your commits). What they make awkward is day-to-day branch work. `oms` keeps the native benefits and removes the friction:
|
|
12
12
|
|
|
13
|
-
- **Create a new branch locally, with no remote precondition.** `oms
|
|
13
|
+
- **Create a new branch locally, with no remote precondition.** `oms switch <alias> <branch>` makes the branch right away even if it does not exist on the remote yet; `oms checkout <alias> <branch>` fetches origin and checks out an existing remote branch as a tracking branch. The remote branch is created lazily on your first `oms push`.
|
|
14
14
|
- **Always on a branch, never detached.** `oms sync` attaches the baseline branch at the pinned commit instead of leaving a detached HEAD.
|
|
15
15
|
- **Pointer moves stay visible.** `oms pull` / `oms push` stage the updated gitlink in the parent so you can see and commit it; `oms status` surfaces drift.
|
|
16
16
|
|
|
@@ -67,16 +67,19 @@ oms sync --list # list registered source repos
|
|
|
67
67
|
oms sync <alias>... # add/initialize submodules on their baseline branch
|
|
68
68
|
oms sync --all # sync every registered source repo
|
|
69
69
|
oms status # branch / pointer / dirty / ahead-behind per submodule
|
|
70
|
-
oms
|
|
70
|
+
oms switch <alias> <branch> # switch to a local branch, or create a brand-new one
|
|
71
|
+
oms checkout <alias> <branch> # fetch origin, then check out a remote branch (tracking)
|
|
71
72
|
oms push <alias>... # push the branch (lazy remote) + stage the pointer
|
|
72
73
|
oms push <alias> --remote upstream # target a specific remote (repeatable; defaults to origin)
|
|
73
74
|
oms unsync <alias>... # deinitialize and remove a submodule
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
For `oms switch` and `oms checkout`, omitting the alias and/or branch prompts you to pick one interactively (synced submodules, and local or `origin/*` branches respectively).
|
|
78
|
+
|
|
76
79
|
A typical new-branch flow:
|
|
77
80
|
|
|
78
81
|
```bash
|
|
79
|
-
oms
|
|
82
|
+
oms switch api feature/login # local branch, no remote needed
|
|
80
83
|
# ... edit, commit inside oms/api ...
|
|
81
84
|
oms push api # creates origin/feature/login and stages the pointer
|
|
82
85
|
git add oms/api && git commit # record the pointer in your project history
|
|
@@ -104,7 +107,8 @@ oms/
|
|
|
104
107
|
| `oms doctor` | project root or child path | Checks `oms.yaml`, git availability, that the workspace is a git repo, and each alias's submodule state. | Returns exit 2 if any warning is raised. |
|
|
105
108
|
| `oms sync <alias>` / `--all` | workspace root | Registers missing repos with `git submodule add`, initializes registered-but-uninitialized ones, fetches, and attaches the baseline branch. | Reproduces the recorded pointer on a fresh clone. |
|
|
106
109
|
| `oms status [alias...]` / `--all` | anywhere under root | Prints branch, pointer state (`ok`/`moved`/`uninit`), dirtiness, and ahead/behind for each submodule. | `moved` means the working commit differs from the recorded pointer — stage/commit it. |
|
|
107
|
-
| `oms
|
|
110
|
+
| `oms switch [alias] [branch]` | workspace root | `git switch` to a LOCAL branch, creating it locally if it does not exist yet (no remote required). | `--from <ref>` sets the start point for a new branch. Omit alias/branch to pick interactively (or create a new branch). |
|
|
111
|
+
| `oms checkout [alias] [branch]` | workspace root | `git fetch origin --prune`, then check out a REMOTE branch (`origin/*`) as a local tracking branch (or switch to an existing local counterpart). | Omit alias/branch to pick interactively. To create a brand-new local branch, use `oms switch`. |
|
|
108
112
|
| `oms fetch ...` | workspace root | `git fetch <remote> --prune` in each submodule. | `--remote <name>` (repeatable) picks the remote(s); omit to choose interactively, defaults to `origin`. |
|
|
109
113
|
| `oms pull ...` | workspace root | `git pull --ff-only <remote>` on each submodule's current branch, then stages the moved pointer. | Requires the submodule to be on a branch. `--remote <name>` selects a single remote (defaults to `origin`). |
|
|
110
114
|
| `oms push <alias>...` | workspace root | `git push <remote> <branch>` (creating the remote branch on first push), then stages the moved pointer. | `--remote <name>` (repeatable) picks the remote(s), defaults to `origin`; upstream is set only for `origin`. `--commit` also commits the pointer update in the parent. |
|
|
@@ -128,7 +132,12 @@ repos:
|
|
|
128
132
|
Rules:
|
|
129
133
|
|
|
130
134
|
- `repos` must be a non-empty array.
|
|
131
|
-
- `alias` must be unique
|
|
135
|
+
- `alias` must be unique.
|
|
136
|
+
- Used as the directory name under `oms/<alias>/`.
|
|
137
|
+
- First character: ASCII lowercase letter or digit.
|
|
138
|
+
- Remaining characters: ASCII lowercase letters, digits, `-`, `_`, `@`.
|
|
139
|
+
- Not allowed: uppercase letters, `/`, `\`, `.`, whitespace.
|
|
140
|
+
- Pattern: `/^[a-z0-9][a-z0-9_@-]*$/`.
|
|
132
141
|
- `remotes` is required and must include an `origin` entry; each value is a clonable git URL. `origin` becomes the submodule's primary remote, and additional remotes are configured on `oms sync`.
|
|
133
142
|
- `branch` is optional; when omitted the remote's default branch is used as the baseline.
|
|
134
143
|
|
|
@@ -138,6 +147,7 @@ JSON schema: [`oms.schema.json`](./oms.schema.json) (also reachable at `https://
|
|
|
138
147
|
|
|
139
148
|
Detailed migration steps are organized per version under [`docs/migrations/`](./docs/migrations/).
|
|
140
149
|
|
|
150
|
+
- [0.7.x → 0.8.0](./docs/migrations/0.7.x-to-0.8.0.md) — splits `oms checkout` into `oms switch` (local branches) and `oms checkout` (remote branches)
|
|
141
151
|
- [0.5.x → 0.6.0](./docs/migrations/0.5.x-to-0.6.0.md) — switches the data model from bare clone + worktrees back to git submodules
|
|
142
152
|
- [0.3.x → 0.4.0](./docs/migrations/0.3.x-to-0.4.0.md) — renames `sources.yaml`/`sources/` to `oms.yaml`/`oms/`
|
|
143
153
|
- [0.2.x → 0.3.0](./docs/migrations/0.2.x-to-0.3.0.md) — (historical) switched submodules to bare clone + worktrees
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"b2c2b0bb4e51a35fc36da6d65f40229c896f1d70"}
|
package/dist/oms.js
CHANGED
|
@@ -4,9 +4,9 @@ import { existsSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import { cancel, isCancel, log, multiselect, select } from "@clack/prompts";
|
|
7
|
+
import { cancel, isCancel, log, multiselect, select, text } from "@clack/prompts";
|
|
8
8
|
import { parse as parseYaml } from "yaml";
|
|
9
|
-
const ALIAS_PATTERN = /^[a-z0-9][a-z0-
|
|
9
|
+
const ALIAS_PATTERN = /^[a-z0-9][a-z0-9_@-]*$/;
|
|
10
10
|
const REMOTE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
11
11
|
const ALLOWED_TOP_KEYS = new Set(["repos"]);
|
|
12
12
|
const ALLOWED_ITEM_KEYS = new Set(["alias", "remotes", "branch"]);
|
|
@@ -138,6 +138,88 @@ async function selectInteractive(repos, actionLabel) {
|
|
|
138
138
|
.map((alias) => repos.find((r) => r.alias === alias))
|
|
139
139
|
.filter((r) => r !== undefined);
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a single alias for a per-repo branch command (switch/checkout). An explicit alias is
|
|
143
|
+
* validated and must be a synced submodule; when omitted, the user picks one interactively from the
|
|
144
|
+
* synced submodules. Returns null (with a clear message) on an unknown/unsynced alias, an empty set,
|
|
145
|
+
* a non-interactive shell, or cancellation.
|
|
146
|
+
*/
|
|
147
|
+
async function resolveInitializedAlias(repos, repoRoot, alias, actionLabel) {
|
|
148
|
+
if (alias) {
|
|
149
|
+
const repo = repos.find((r) => r.alias === alias);
|
|
150
|
+
if (!repo) {
|
|
151
|
+
log.error(`Unknown alias "${alias}". Use "oms sync --list" to see registered aliases.`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
if (!submoduleInitialized(repoRoot, alias)) {
|
|
155
|
+
log.error(`${alias}: not synced. Run "oms sync ${alias}" first.`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return repo;
|
|
159
|
+
}
|
|
160
|
+
const initialized = repos.filter((r) => submoduleInitialized(repoRoot, r.alias));
|
|
161
|
+
if (initialized.length === 0) {
|
|
162
|
+
log.error(`No synced submodules to ${actionLabel}. Run "oms sync" first.`);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
if (!process.stdin.isTTY) {
|
|
166
|
+
log.error(`No alias given and stdin is not a TTY. Pass an alias: "oms ${actionLabel} <alias>".`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const choice = await select({
|
|
170
|
+
message: `Select a source repo to ${actionLabel}`,
|
|
171
|
+
options: initialized.map((r) => ({
|
|
172
|
+
value: r.alias,
|
|
173
|
+
label: r.alias,
|
|
174
|
+
hint: r.branch ? `branch: ${r.branch}` : undefined,
|
|
175
|
+
})),
|
|
176
|
+
});
|
|
177
|
+
if (isCancel(choice)) {
|
|
178
|
+
cancel("Cancelled.");
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return initialized.find((r) => r.alias === choice) ?? null;
|
|
182
|
+
}
|
|
183
|
+
/** Sentinel chosen in pickBranch to create a new branch instead of selecting an existing one. */
|
|
184
|
+
const CREATE_NEW_BRANCH = "\0create-new-branch";
|
|
185
|
+
/**
|
|
186
|
+
* Prompt for a branch from the given list. When allowCreate is set, a "create new branch" option
|
|
187
|
+
* collects a name via a text prompt. Returns null (with a clear message) on a non-interactive shell,
|
|
188
|
+
* an empty list with no create option, an empty name, or cancellation.
|
|
189
|
+
*/
|
|
190
|
+
async function pickBranch(branches, message, allowCreate) {
|
|
191
|
+
if (!process.stdin.isTTY) {
|
|
192
|
+
log.error(`No branch given and stdin is not a TTY. Pass a branch name explicitly.`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (branches.length === 0 && !allowCreate) {
|
|
196
|
+
log.error(`No branches available to select.`);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const options = [
|
|
200
|
+
...(allowCreate ? [{ value: CREATE_NEW_BRANCH, label: "+ create new branch" }] : []),
|
|
201
|
+
...branches.map((b) => ({ value: b, label: b })),
|
|
202
|
+
];
|
|
203
|
+
const choice = await select({ message, options });
|
|
204
|
+
if (isCancel(choice)) {
|
|
205
|
+
cancel("Cancelled.");
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (choice === CREATE_NEW_BRANCH) {
|
|
209
|
+
const name = await text({ message: "New branch name", placeholder: "feature/login" });
|
|
210
|
+
if (isCancel(name)) {
|
|
211
|
+
cancel("Cancelled.");
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const trimmed = name.trim();
|
|
215
|
+
if (!trimmed) {
|
|
216
|
+
log.error("Branch name is empty.");
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
return trimmed;
|
|
220
|
+
}
|
|
221
|
+
return choice;
|
|
222
|
+
}
|
|
141
223
|
/**
|
|
142
224
|
* Decide which remote(s) a fetch/pull/push targets for one repo. Honors an explicit --remote list,
|
|
143
225
|
* otherwise prompts interactively on a TTY (origin preselected) and falls back to origin off-TTY.
|
|
@@ -286,6 +368,25 @@ function localBranchExists(dir, branch) {
|
|
|
286
368
|
function remoteBranchExists(dir, branch) {
|
|
287
369
|
return runGit(dir, ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${branch}`]).success;
|
|
288
370
|
}
|
|
371
|
+
/** Local branch short names (refs/heads), e.g. ["main", "dev"]. Empty on failure or none. */
|
|
372
|
+
function listLocalBranches(dir) {
|
|
373
|
+
const r = runGit(dir, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
|
|
374
|
+
if (!r.success)
|
|
375
|
+
return [];
|
|
376
|
+
return r.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
377
|
+
}
|
|
378
|
+
/** Remote branch short names under origin, with the "origin/" prefix stripped and origin/HEAD excluded. */
|
|
379
|
+
function listRemoteBranches(dir) {
|
|
380
|
+
const r = runGit(dir, ["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin"]);
|
|
381
|
+
if (!r.success)
|
|
382
|
+
return [];
|
|
383
|
+
return r.stdout
|
|
384
|
+
.split("\n")
|
|
385
|
+
.map((s) => s.trim())
|
|
386
|
+
.filter((s) => s.length > 0 && s !== "origin/HEAD")
|
|
387
|
+
.map((s) => (s.startsWith("origin/") ? s.slice("origin/".length) : s))
|
|
388
|
+
.filter((s) => s !== "HEAD");
|
|
389
|
+
}
|
|
289
390
|
function isDirty(dir) {
|
|
290
391
|
const r = runGit(dir, ["status", "--porcelain"]);
|
|
291
392
|
return r.success && r.stdout.trim().length > 0;
|
|
@@ -591,7 +692,7 @@ function pullRepo(repo, repoRoot, remote) {
|
|
|
591
692
|
}
|
|
592
693
|
const branch = currentBranch(aliasDir(repoRoot, repo.alias));
|
|
593
694
|
if (!branch) {
|
|
594
|
-
log.error(`${repo.alias}: detached HEAD. Run "oms
|
|
695
|
+
log.error(`${repo.alias}: detached HEAD. Run "oms switch ${repo.alias} <branch>" before pulling.`);
|
|
595
696
|
return "failed";
|
|
596
697
|
}
|
|
597
698
|
log.step(`${repo.alias}/${branch}: git pull --ff-only ${remote} ${branch}`);
|
|
@@ -611,7 +712,7 @@ function pushRepo(repo, repoRoot, remotes, commit) {
|
|
|
611
712
|
}
|
|
612
713
|
const branch = currentBranch(aliasDir(repoRoot, repo.alias));
|
|
613
714
|
if (!branch) {
|
|
614
|
-
log.error(`${repo.alias}: detached HEAD. Run "oms
|
|
715
|
+
log.error(`${repo.alias}: detached HEAD. Run "oms switch ${repo.alias} <branch>" before pushing.`);
|
|
615
716
|
return "failed";
|
|
616
717
|
}
|
|
617
718
|
for (const remote of remotes) {
|
|
@@ -792,47 +893,87 @@ async function runUnsync(aliases, options) {
|
|
|
792
893
|
}
|
|
793
894
|
return exitFromResults(results);
|
|
794
895
|
}
|
|
795
|
-
|
|
896
|
+
/**
|
|
897
|
+
* LOCAL branch management: switch the submodule to an existing local branch or create a new one.
|
|
898
|
+
* No remote is consulted — creating a brand-new branch needs no remote precondition and sets no
|
|
899
|
+
* upstream (that is checkout's job). Omitting alias and/or branch prompts for them interactively.
|
|
900
|
+
*/
|
|
901
|
+
async function runSwitch(alias, branch, options) {
|
|
796
902
|
const loaded = loadForSubmodules();
|
|
797
903
|
if (!loaded)
|
|
798
904
|
return 1;
|
|
799
905
|
const { repos, repoRoot } = loaded;
|
|
800
|
-
const repo = repos
|
|
801
|
-
if (!repo)
|
|
802
|
-
log.error(`Unknown alias "${alias}". Use "oms sync --list" to see registered aliases.`);
|
|
906
|
+
const repo = await resolveInitializedAlias(repos, repoRoot, alias, "switch");
|
|
907
|
+
if (!repo)
|
|
803
908
|
return 1;
|
|
909
|
+
const dir = aliasDir(repoRoot, repo.alias);
|
|
910
|
+
let target = branch;
|
|
911
|
+
if (!target) {
|
|
912
|
+
const picked = await pickBranch(listLocalBranches(dir), `${repo.alias}: select a local branch`, true);
|
|
913
|
+
if (!picked)
|
|
914
|
+
return 1;
|
|
915
|
+
target = picked;
|
|
916
|
+
}
|
|
917
|
+
if (localBranchExists(dir, target)) {
|
|
918
|
+
log.step(`${repo.alias}: git switch ${target}`);
|
|
919
|
+
const r = runSub(repoRoot, repo.alias, ["switch", target], true);
|
|
920
|
+
if (!r.success)
|
|
921
|
+
return 2;
|
|
922
|
+
log.success(`${repo.alias}: on ${target}`);
|
|
923
|
+
return 0;
|
|
804
924
|
}
|
|
805
|
-
|
|
806
|
-
|
|
925
|
+
// Brand-new local branch: no remote precondition, no upstream tracking (use "oms checkout" for that).
|
|
926
|
+
const args = ["switch", "-c", target, ...(options.from ? [options.from] : [])];
|
|
927
|
+
log.step(`${repo.alias}: git ${args.join(" ")}`);
|
|
928
|
+
const r = runSub(repoRoot, repo.alias, args, true);
|
|
929
|
+
if (!r.success)
|
|
930
|
+
return 2;
|
|
931
|
+
log.success(`${repo.alias}: created new local branch ${target}. Push it with "oms push ${repo.alias}".`);
|
|
932
|
+
return 0;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* REMOTE branch exploration: fetch origin, then check out a remote branch as a local tracking
|
|
936
|
+
* branch (or switch to an existing local counterpart). Omitting alias and/or branch prompts for
|
|
937
|
+
* them interactively. Creating brand-new local branches is "oms switch"'s job.
|
|
938
|
+
*/
|
|
939
|
+
async function runCheckout(alias, branch) {
|
|
940
|
+
const loaded = loadForSubmodules();
|
|
941
|
+
if (!loaded)
|
|
807
942
|
return 1;
|
|
808
|
-
}
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
943
|
+
const { repos, repoRoot } = loaded;
|
|
944
|
+
const repo = await resolveInitializedAlias(repos, repoRoot, alias, "checkout");
|
|
945
|
+
if (!repo)
|
|
946
|
+
return 1;
|
|
947
|
+
const dir = aliasDir(repoRoot, repo.alias);
|
|
948
|
+
log.step(`${repo.alias}: git fetch origin --prune`);
|
|
949
|
+
const fetch = runSub(repoRoot, repo.alias, ["fetch", "origin", "--prune"], true);
|
|
950
|
+
if (!fetch.success)
|
|
951
|
+
return 2;
|
|
952
|
+
let target = branch;
|
|
953
|
+
if (!target) {
|
|
954
|
+
const picked = await pickBranch(listRemoteBranches(dir), `${repo.alias}: select a remote branch (origin/*)`, false);
|
|
955
|
+
if (!picked)
|
|
956
|
+
return 1;
|
|
957
|
+
target = picked;
|
|
958
|
+
}
|
|
959
|
+
if (localBranchExists(dir, target)) {
|
|
960
|
+
log.step(`${repo.alias}: git switch ${target}`);
|
|
961
|
+
const r = runSub(repoRoot, repo.alias, ["switch", target], true);
|
|
815
962
|
if (!r.success)
|
|
816
963
|
return 2;
|
|
817
|
-
log.success(`${alias}: on ${
|
|
964
|
+
log.success(`${repo.alias}: on ${target}`);
|
|
818
965
|
return 0;
|
|
819
966
|
}
|
|
820
|
-
if (remoteBranchExists(dir,
|
|
821
|
-
log.step(`${alias}: git switch -c ${
|
|
822
|
-
const r = runSub(repoRoot, alias, ["switch", "-c",
|
|
967
|
+
if (remoteBranchExists(dir, target)) {
|
|
968
|
+
log.step(`${repo.alias}: git switch -c ${target} origin/${target}`);
|
|
969
|
+
const r = runSub(repoRoot, repo.alias, ["switch", "-c", target, `origin/${target}`], true);
|
|
823
970
|
if (!r.success)
|
|
824
971
|
return 2;
|
|
825
|
-
log.success(`${alias}: on ${
|
|
972
|
+
log.success(`${repo.alias}: on ${target} (tracking origin/${target})`);
|
|
826
973
|
return 0;
|
|
827
974
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
log.step(`${alias}: git ${args.join(" ")}`);
|
|
831
|
-
const r = runSub(repoRoot, alias, args, true);
|
|
832
|
-
if (!r.success)
|
|
833
|
-
return 2;
|
|
834
|
-
log.success(`${alias}: created new local branch ${branch}. Push it with "oms push ${alias}".`);
|
|
835
|
-
return 0;
|
|
975
|
+
log.error(`${repo.alias}: "${target}" not found on origin. To create a new local branch, run "oms switch ${repo.alias} ${target}".`);
|
|
976
|
+
return 1;
|
|
836
977
|
}
|
|
837
978
|
/** Parse the leading status char from `git submodule status` (' ' ok, '+' moved, '-' uninit, 'U' conflict). */
|
|
838
979
|
function pinState(repoRoot, alias) {
|
|
@@ -978,7 +1119,7 @@ async function runDoctor() {
|
|
|
978
1119
|
const dir = aliasDir(repoRoot, repo.alias);
|
|
979
1120
|
const branch = currentBranch(dir);
|
|
980
1121
|
if (!branch) {
|
|
981
|
-
log.warn(`${repo.alias}: detached HEAD. Run "oms
|
|
1122
|
+
log.warn(`${repo.alias}: detached HEAD. Run "oms switch ${repo.alias} <branch>" to get on a branch.`);
|
|
982
1123
|
warnings++;
|
|
983
1124
|
}
|
|
984
1125
|
else {
|
|
@@ -1024,6 +1165,7 @@ const commandNames = new Set([
|
|
|
1024
1165
|
"doctor",
|
|
1025
1166
|
"sync",
|
|
1026
1167
|
"status",
|
|
1168
|
+
"switch",
|
|
1027
1169
|
"checkout",
|
|
1028
1170
|
"fetch",
|
|
1029
1171
|
"pull",
|
|
@@ -1072,14 +1214,23 @@ program
|
|
|
1072
1214
|
await exitWith(runStatus(aliases, options));
|
|
1073
1215
|
});
|
|
1074
1216
|
program
|
|
1075
|
-
.command("
|
|
1076
|
-
.description("Switch a submodule to
|
|
1077
|
-
.argument("
|
|
1078
|
-
.argument("
|
|
1217
|
+
.command("switch")
|
|
1218
|
+
.description("Switch a submodule to a LOCAL branch, creating it locally if it does not exist yet (no remote required).")
|
|
1219
|
+
.argument("[alias]", "registered source alias (omit to pick interactively)")
|
|
1220
|
+
.argument("[branch]", "local branch name (omit to pick from local branches or create one)")
|
|
1079
1221
|
.option("--from <ref>", "start point for a new branch (default: current HEAD)")
|
|
1080
1222
|
.addHelpText("after", exitHelp)
|
|
1081
1223
|
.action(async (alias, branch, options) => {
|
|
1082
|
-
await exitWith(
|
|
1224
|
+
await exitWith(runSwitch(alias, branch, options));
|
|
1225
|
+
});
|
|
1226
|
+
program
|
|
1227
|
+
.command("checkout")
|
|
1228
|
+
.description("Fetch origin, then check out a REMOTE branch (origin/*) as a local tracking branch.")
|
|
1229
|
+
.argument("[alias]", "registered source alias (omit to pick interactively)")
|
|
1230
|
+
.argument("[branch]", "remote branch name (omit to pick from origin/* branches)")
|
|
1231
|
+
.addHelpText("after", exitHelp)
|
|
1232
|
+
.action(async (alias, branch) => {
|
|
1233
|
+
await exitWith(runCheckout(alias, branch));
|
|
1083
1234
|
});
|
|
1084
1235
|
const collectRemote = (value, acc) => [...acc, value];
|
|
1085
1236
|
program
|