Merge main into wj before taking wilm's work
wj held nothing main lacked - git cherry marks every one of its commits as already present - but promotion cherry-picks, so the histories diverged and a fast-forward is not possible. Merging rather than resetting keeps the branch recoverable and needs no force-push. Levelling first so wilm's patches apply against current product code. # Conflicts: # CONTRIBUTING.md # frontend/src/views/Dashboard.vue # tools/wj-status.sh
This commit is contained in:
@@ -153,9 +153,15 @@ Tests are required for:
|
|||||||
|
|
||||||
## Branches: what belongs on main, and what does not
|
## Branches: what belongs on main, and what does not
|
||||||
|
|
||||||
Two branches. `main` is the product every site runs and the publication mirror
|
`main` is the product every site runs and the publication mirror exports.
|
||||||
exports. `<site>` branches - currently `wj` - hold work that is not ready for
|
`<site>` branches hold work that is not ready for other sites to see. There are
|
||||||
other sites to see.
|
two: `wj` here, and `wilm` on the corporate GitHub, where the other site
|
||||||
|
develops the .NET endpoint agent.
|
||||||
|
|
||||||
|
A site branch is not a fork. Both feed `main`, and the sooner they do it the
|
||||||
|
cheaper it is. A branch the dev box cannot see is still a branch: `wilm` is not
|
||||||
|
visible from here because this machine has no GitHub account, which is why
|
||||||
|
`fetch-wilm-branch.cmd` on the share exists to bring it back for review.
|
||||||
|
|
||||||
**Most work is product.** ADR-015 gives site differences three homes on `main`:
|
**Most work is product.** ADR-015 gives site differences three homes on `main`:
|
||||||
a setting with a neutral default, seed data, or a site-namespaced directory
|
a setting with a neutral default, seed data, or a site-namespaced directory
|
||||||
@@ -188,10 +194,9 @@ quietly becomes the product.
|
|||||||
|
|
||||||
**The failure to watch for is not what you think.** Promoting something
|
**The failure to watch for is not what you think.** Promoting something
|
||||||
site-specific by accident is caught by the ADR-015 gate on the way into `main`.
|
site-specific by accident is caught by the ADR-015 gate on the way into `main`.
|
||||||
The real risk is product work sitting on a site branch forever because nobody
|
The real risk is product work sitting on a site branch until bringing it back is
|
||||||
decided - which is how the `wilm` branch stopped being a merge and became a
|
a port rather than a merge - one such round trip cost a MySQL dialect fix, a
|
||||||
port, costing a MySQL dialect fix, a contract violation and a plugin install to
|
contract violation and a plugin install.
|
||||||
bring back.
|
|
||||||
|
|
||||||
So rebase the site branch on `main` often, and promote as you go rather than in
|
So rebase the site branch on `main` often, and promote as you go rather than in
|
||||||
one batch at the end.
|
one batch at the end.
|
||||||
|
|||||||
@@ -90,6 +90,6 @@ Manifest-less directories under `plugins/` are core frontend surface and always
|
|||||||
|
|
||||||
## Size
|
## Size
|
||||||
|
|
||||||
- test functions defined: **1503** (parametrised cases collect higher)
|
- test functions defined: **1507** (parametrised cases collect higher)
|
||||||
- documented API paths: **333** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
- documented API paths: **333** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
<template #rail>
|
<template #rail>
|
||||||
<!-- Only when the totals actually loaded. /api/dashboard/summary
|
<!-- Only when the totals actually loaded. /api/dashboard/summary
|
||||||
needs assets.view, and a caller without it got an empty object
|
needs assets.view, and a caller without it got an empty object
|
||||||
that rendered as a tidy column of zeros - an estate of nothing,
|
that rendered as a tidy column of zeros - a site with nothing
|
||||||
stated with total confidence. Same fault as reporting Low
|
in it, stated with total confidence. Same fault as reporting Low
|
||||||
Supplies 0 while the monitoring is down. -->
|
Supplies 0 while the monitoring is down. -->
|
||||||
<section v-if="summaryloaded" class="dc-panel estate">
|
<section v-if="summaryloaded" class="dc-panel assets">
|
||||||
<header class="estate-head">
|
<header class="assets-head">
|
||||||
<h3>Estate</h3>
|
<h3>Assets</h3>
|
||||||
<router-link to="/reports" class="estate-link">Reports</router-link>
|
<router-link to="/reports" class="assets-link">Reports</router-link>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- One line per asset type, then the total under a rule.
|
<!-- One line per asset type, then the total under a rule.
|
||||||
@@ -37,21 +37,21 @@
|
|||||||
EVERY counted type must appear here. The endpoint's grand total
|
EVERY counted type must appear here. The endpoint's grand total
|
||||||
is machines + PCs + network + printers + measuring tools, and
|
is machines + PCs + network + printers + measuring tools, and
|
||||||
omitting one leaves a total nothing on screen explains. -->
|
omitting one leaves a total nothing on screen explains. -->
|
||||||
<div class="estate-row">
|
<div class="assets-row">
|
||||||
<router-link to="/machines" class="estate-k">Machines</router-link>
|
<router-link to="/machines" class="assets-k">Machines</router-link>
|
||||||
<span class="estate-v">{{ stats.totalmachines || 0 }}</span>
|
<span class="assets-v">{{ stats.totalmachines || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="estate-row">
|
<div class="assets-row">
|
||||||
<router-link to="/pcs" class="estate-k">PCs</router-link>
|
<router-link to="/pcs" class="assets-k">PCs</router-link>
|
||||||
<span class="estate-v">{{ stats.totalpc || 0 }}</span>
|
<span class="assets-v">{{ stats.totalpc || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="estate-row">
|
<div class="assets-row">
|
||||||
<router-link to="/printers" class="estate-k">Printers</router-link>
|
<router-link to="/printers" class="assets-k">Printers</router-link>
|
||||||
<span class="estate-v">{{ stats.totalprinter || 0 }}</span>
|
<span class="assets-v">{{ stats.totalprinter || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="estate-row">
|
<div class="assets-row">
|
||||||
<router-link to="/network" class="estate-k">Network devices</router-link>
|
<router-link to="/network" class="assets-k">Network devices</router-link>
|
||||||
<span class="estate-v">{{ stats.totalnetwork || 0 }}</span>
|
<span class="assets-v">{{ stats.totalnetwork || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Measuring tools are IN the grand total the endpoint returns, so
|
<!-- Measuring tools are IN the grand total the endpoint returns, so
|
||||||
they have to be visible here. Left out, the total silently
|
they have to be visible here. Left out, the total silently
|
||||||
@@ -59,24 +59,34 @@
|
|||||||
729 on an instance with gages - which is the exact fault this
|
729 on an instance with gages - which is the exact fault this
|
||||||
panel was built to remove. Dev has none, so it looked correct
|
panel was built to remove. Dev has none, so it looked correct
|
||||||
here and was wrong everywhere the import had run. -->
|
here and was wrong everywhere the import had run. -->
|
||||||
<div class="estate-row">
|
<div class="assets-row">
|
||||||
<router-link to="/measuringtools" class="estate-k">Measuring tools</router-link>
|
<router-link to="/measuringtools" class="assets-k">Measuring tools</router-link>
|
||||||
<span class="estate-v">{{ stats.totalmeasuringtool || 0 }}</span>
|
<span class="assets-v">{{ stats.totalmeasuringtool || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="estate-row estate-total">
|
<div class="assets-row assets-total">
|
||||||
<span class="estate-k">All assets</span>
|
<span class="assets-k">All assets</span>
|
||||||
<span class="estate-v">{{ stats.totalassets || 0 }}</span>
|
<span class="assets-v">{{ stats.totalassets || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status, on one line, and explicitly labelled as status. In
|
<!-- Every status the total contains, not just In Use. These were
|
||||||
repair is muted at zero rather than given a card of its own to
|
counted server-side for the rollup and then thrown away, so a
|
||||||
announce that nothing is wrong. -->
|
reader could see one number and had no way to learn where the
|
||||||
<p class="estate-status">
|
rest of the total went. Retired assets in particular ARE in
|
||||||
<span class="estate-inuse">{{ stats.activeassets || 0 }}</span> in use
|
the total, which is not guessable. -->
|
||||||
<span aria-hidden="true"> / </span>
|
<div v-for="row in statusrows" :key="row.name" class="assets-row assets-sub">
|
||||||
<span :class="{ 'estate-zero': !stats.inrepair }">
|
<span class="assets-k assets-subk">{{ row.name }}</span>
|
||||||
{{ stats.inrepair || 0 }} in repair
|
<span class="assets-v assets-subv">{{ row.count }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- What the total covers. Asked twice what the number meant, which
|
||||||
|
is the signal a label is underspecified. -->
|
||||||
|
<p class="assets-caption">
|
||||||
|
Active records only<span v-if="stats.dualpathcollapsed">, dual-bay
|
||||||
|
pairs counted once</span>.
|
||||||
|
<span v-if="stats.inactiveexcluded">
|
||||||
|
{{ stats.inactiveexcluded }} deactivated record{{ stats.inactiveexcluded === 1 ? '' : 's' }}
|
||||||
|
excluded.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -89,12 +99,12 @@
|
|||||||
uses - server-filtered for a signed-out visitor - so it can never
|
uses - server-filtered for a signed-out visitor - so it can never
|
||||||
offer a link that dead-ends at the router guard, and it names no
|
offer a link that dead-ends at the router guard, and it names no
|
||||||
plugin this page had to know about. -->
|
plugin this page had to know about. -->
|
||||||
<section v-else-if="openable.length" class="dc-panel estate">
|
<section v-else-if="openable.length" class="dc-panel assets">
|
||||||
<header class="estate-head">
|
<header class="assets-head">
|
||||||
<h3>Where you can go</h3>
|
<h3>Where you can go</h3>
|
||||||
</header>
|
</header>
|
||||||
<div v-for="item in openable" :key="item.route" class="estate-row">
|
<div v-for="item in openable" :key="item.route" class="assets-row">
|
||||||
<router-link :to="item.route" class="estate-k">{{ item.name }}</router-link>
|
<router-link :to="item.route" class="assets-k">{{ item.name }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -133,6 +143,17 @@ const summaryloaded = ref(false)
|
|||||||
// hold - and to what the guest role reaches, when nobody is signed in.
|
// hold - and to what the guest role reaches, when nobody is signed in.
|
||||||
const navitems = ref([])
|
const navitems = ref([])
|
||||||
|
|
||||||
|
// Every status in the total, biggest first. Sorted rather than fixed-order
|
||||||
|
// because the statuses are seed data a site can change - hardcoding "In Use,
|
||||||
|
// In Repair, Retired" would silently omit a status somebody added.
|
||||||
|
const statusrows = computed(() => {
|
||||||
|
const statuses = stats.value.statuses || {}
|
||||||
|
return Object.keys(statuses)
|
||||||
|
.map((name) => ({ name, count: statuses[name] }))
|
||||||
|
.filter((row) => row.count > 0)
|
||||||
|
.sort((a, b) => b.count - a.count)
|
||||||
|
})
|
||||||
|
|
||||||
const openable = computed(
|
const openable = computed(
|
||||||
// The dashboard itself is not somewhere to go from the dashboard.
|
// The dashboard itself is not somewhere to go from the dashboard.
|
||||||
() => navitems.value.filter((item) => item.route && item.route !== '/'))
|
() => navitems.value.filter((item) => item.route && item.route !== '/'))
|
||||||
@@ -165,7 +186,7 @@ onMounted(async () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Matches the panels in DashboardCards; the rail reads as one column rather
|
/* Matches the panels in DashboardCards; the rail reads as one column rather
|
||||||
than as a page card that happens to sit beside them. */
|
than as a page card that happens to sit beside them. */
|
||||||
.estate {
|
.assets {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -173,13 +194,13 @@ onMounted(async () => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.estate-head {
|
.assets-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
.estate-head h3 {
|
.assets-head h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -188,14 +209,14 @@ onMounted(async () => {
|
|||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
.estate-link {
|
.assets-link {
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.estate-link:hover { text-decoration: underline; }
|
.assets-link:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.estate-row {
|
.assets-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -203,32 +224,39 @@ onMounted(async () => {
|
|||||||
padding: 0.35rem 0;
|
padding: 0.35rem 0;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.estate-row:last-of-type { border-bottom: none; }
|
.assets-row:last-of-type { border-bottom: none; }
|
||||||
|
|
||||||
.estate-k {
|
.assets-k {
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
a.estate-k:hover { text-decoration: underline; }
|
a.assets-k:hover { text-decoration: underline; }
|
||||||
span.estate-k { color: var(--text); font-weight: 600; }
|
span.assets-k { color: var(--text); font-weight: 600; }
|
||||||
|
|
||||||
.estate-v {
|
.assets-v {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.estate-total {
|
.assets-total {
|
||||||
margin-top: 0.35rem;
|
margin-top: 0.35rem;
|
||||||
border-top: 2px solid var(--border);
|
border-top: 2px solid var(--border);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.estate-status {
|
/* Status rows sit UNDER the total, indented and quieter, because they break
|
||||||
margin: 0.5rem 0 0;
|
the total down rather than adding to it. Same weight as the type rows would
|
||||||
font-size: 0.85rem;
|
read as more things to add up. */
|
||||||
|
.assets-sub { border-bottom: none; padding: 0.15rem 0 0.15rem 0.75rem; }
|
||||||
|
.assets-subk { color: var(--text-light); font-size: 0.85rem; }
|
||||||
|
a.assets-subk:hover { text-decoration: none; }
|
||||||
|
.assets-subv { font-weight: 500; font-size: 0.85rem; color: var(--text-light); }
|
||||||
|
|
||||||
|
.assets-caption {
|
||||||
|
margin: 0.6rem 0 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.45;
|
||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
}
|
}
|
||||||
.estate-inuse { color: var(--success); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
||||||
.estate-zero { color: var(--text-light); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -38,6 +38,19 @@ def _count_by_type(assettype):
|
|||||||
return query.count()
|
return query.count()
|
||||||
|
|
||||||
|
|
||||||
|
def _inactive_excluded():
|
||||||
|
"""Counted-type assets held back by isactive, i.e. soft-deleted.
|
||||||
|
|
||||||
|
Reported so the page can account for itself. A total that silently drops
|
||||||
|
rows is the reason somebody compares it with a list page and finds a
|
||||||
|
difference they cannot explain.
|
||||||
|
"""
|
||||||
|
return (db.session.query(Asset).join(AssetType)
|
||||||
|
.filter(Asset.isactive == False,
|
||||||
|
AssetType.assettype.in_(COUNTEDTYPES))
|
||||||
|
.count())
|
||||||
|
|
||||||
|
|
||||||
COUNTEDTYPES = ('machine', 'computer', 'network_device', 'printer',
|
COUNTEDTYPES = ('machine', 'computer', 'network_device', 'printer',
|
||||||
'measuring_tool')
|
'measuring_tool')
|
||||||
|
|
||||||
@@ -69,6 +82,10 @@ def _countedassets():
|
|||||||
@require_permission('assets.view')
|
@require_permission('assets.view')
|
||||||
def get_dashboard():
|
def get_dashboard():
|
||||||
"""Get dashboard summary data (asset-based)."""
|
"""Get dashboard summary data (asset-based)."""
|
||||||
|
# Imported here, as in the helpers above: the dualpath service reaches back
|
||||||
|
# into models, so a module-level import is circular.
|
||||||
|
from shopdb.core.services.dualpath import dualpath_single_machine_enabled
|
||||||
|
|
||||||
machine_count = _count_by_type('machine')
|
machine_count = _count_by_type('machine')
|
||||||
pc_count = _count_by_type('computer')
|
pc_count = _count_by_type('computer')
|
||||||
network_count = _count_by_type('network_device')
|
network_count = _count_by_type('network_device')
|
||||||
@@ -99,6 +116,22 @@ def get_dashboard():
|
|||||||
'totalmeasuringtool': measuringtool_count,
|
'totalmeasuringtool': measuringtool_count,
|
||||||
'activeassets': status_dict.get('In Use', 0),
|
'activeassets': status_dict.get('In Use', 0),
|
||||||
'inrepair': status_dict.get('In Repair', 0),
|
'inrepair': status_dict.get('In Repair', 0),
|
||||||
|
# What the totals actually cover, so the page can say so rather than
|
||||||
|
# leaving a reader to work it out from a number that surprised them.
|
||||||
|
# Every count here filters isactive, and the dual-path collapse is a
|
||||||
|
# per-site toggle - asserting it unconditionally would be false at a
|
||||||
|
# site that has it off.
|
||||||
|
'countsactiveonly': True,
|
||||||
|
'dualpathcollapsed': bool(dualpath_single_machine_enabled()),
|
||||||
|
# Every status, not just the two that had callers. The counts were
|
||||||
|
# already built for the rollup above and then thrown away, so a reader
|
||||||
|
# could see "In Use" and had no way to learn where the rest went.
|
||||||
|
'statuses': status_dict,
|
||||||
|
# A DIFFERENT thing to a status, and the one that makes a total
|
||||||
|
# disagree with a list page. isactive is the soft-delete flag: these
|
||||||
|
# rows are excluded from every count above, so they are in no status
|
||||||
|
# bucket and cannot be found by adding the buckets up.
|
||||||
|
'inactiveexcluded': _inactive_excluded(),
|
||||||
# Structured data
|
# Structured data
|
||||||
'counts': {
|
'counts': {
|
||||||
'machines': machine_count,
|
'machines': machine_count,
|
||||||
|
|||||||
@@ -261,6 +261,31 @@ echo "bundle: $BUNDLE"
|
|||||||
# --- 6. optional per-instance dists (both bases: /ops = dev, /shopdb = prod) ---
|
# --- 6. optional per-instance dists (both bases: /ops = dev, /shopdb = prod) ---
|
||||||
# Two instances run on the box; each needs its own base-path build. Always
|
# Two instances run on the box; each needs its own base-path build. Always
|
||||||
# rebuild BOTH so prod never ships a stale frontend.
|
# rebuild BOTH so prod never ships a stale frontend.
|
||||||
|
# --dist IS MAIN ONLY.
|
||||||
|
#
|
||||||
|
# These paths are not branch-qualified and cannot safely be: update-prod-server
|
||||||
|
# .cmd robocopies frontend-dist-subpath-shopdb onto the production server with
|
||||||
|
# /MIR, a full mirror, and has no idea which branch built it. So --dist from a
|
||||||
|
# site branch would replace production's frontend with unreleased work at the
|
||||||
|
# next deploy, silently, and the deploy script could not tell.
|
||||||
|
#
|
||||||
|
# Branch-qualifying the directory would leave the same trap for whoever pointed
|
||||||
|
# a deploy at the wrong one. Production runs the product; a site branch has no
|
||||||
|
# business producing the artefact production installs.
|
||||||
|
if [ "$BUILD_DIST" = 1 ] && [ "$BRANCH" != "main" ]; then
|
||||||
|
echo "REFUSED: --dist on branch '$BRANCH'." >&2
|
||||||
|
echo >&2
|
||||||
|
echo "The dist directories are what update-prod-server.cmd mirrors onto the" >&2
|
||||||
|
echo "production server, and they are not branch-qualified. Building them" >&2
|
||||||
|
echo "from '$BRANCH' would put unreleased work on production at the next" >&2
|
||||||
|
echo "deploy, with nothing to reveal it." >&2
|
||||||
|
echo >&2
|
||||||
|
echo "Promote the commits to main and export from there:" >&2
|
||||||
|
echo " tools/wj-status.sh" >&2
|
||||||
|
echo " tools/promote-to-main.sh <sha> ..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$BUILD_DIST" = 1 ]; then
|
if [ "$BUILD_DIST" = 1 ]; then
|
||||||
cd "$WORK/frontend"
|
cd "$WORK/frontend"
|
||||||
for base in ops shopdb; do
|
for base in ops shopdb; do
|
||||||
|
|||||||
@@ -44,10 +44,25 @@ if ! git rev-parse --verify --quiet refs/heads/wj >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMMITS="$(git rev-list --reverse "$RANGE")"
|
# git cherry, not rev-list. Promotion CHERRY-PICKS, which gives the commit a new
|
||||||
|
# sha on main, so `main..wj` keeps listing work that already landed - and this
|
||||||
|
# tool would then invite you to promote it a second time. `git cherry` compares
|
||||||
|
# patch content and marks an already-present commit with a leading '-'.
|
||||||
|
COMMITS="$(git cherry main wj | awk '$1 == "+" { print $2 }')"
|
||||||
|
|
||||||
|
ALREADY="$(git cherry main wj | awk '$1 == "-" { print $2 }' | wc -l)"
|
||||||
|
|
||||||
if [ -z "$COMMITS" ]; then
|
if [ -z "$COMMITS" ]; then
|
||||||
echo "Nothing on wj that main does not have."
|
echo "Nothing on wj that main does not have."
|
||||||
|
if [ "$ALREADY" -gt 0 ]; then
|
||||||
|
echo "($ALREADY commit(s) here have already landed on main as cherry-picks.)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "wj is behind main by:"
|
||||||
|
git log --oneline wj..main | sed 's/^/ /' | head -10
|
||||||
|
echo
|
||||||
|
echo "Rebase it before starting new work:"
|
||||||
|
echo " git checkout wj && git rebase main"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user