Leçon 6 · Travail quotidien
Lisez la comparaison avant de juger un changement
Transformez le héros en un projet statique exécutable, puis distinguez les différences de travail, mises en scène et enregistrées. Inspectez une variante d’image déterministe distincte sans ajouter de validation d’image à l’historique continu du cours.
- Niveau: Débutant
- 15 minutes de lecture
- 5 minutes de vidéo
- Mis à jour
- Avant cette leçon: Organisez l'instantané souhaité et effectuez votre premier commit
Progression enregistrée dans ce navigateur.
La lecture charge la vidéo depuis YouTube (mode de confidentialité amélioré).
Ce que vous apprendrez
- Nommer les deux côtés d’une comparaison de texte et interpréter les ajouts/suppressions.
- Expliquez pourquoi un fichier non suivi peut être absent du git diff ordinaire.
- Comparez les images visuellement sans prétendre que Git fournit un patch d'image textuelle.
- Ajoutez et vérifiez les petites vérifications locales avant de valider la page exécutable.
Avant de commencer
Vous commencez là où la leçon 5 s'est terminée : la leçon principale originale avec un engagement de héros. HEAD/index contient HTML-01 ; working index.html contient HTML-01-WORKING. Aucune télécommande n'existe. Conservez cette modification de ligne de support dans HTML-02 ; ne le jetez pas.
La vidéo a été enregistrée avec Git 2.52.0, Node 22.22.0, npm 10.9.4 et GitTree 2.0 sur Ubuntu 24.04 : les dix fichiers ont été enregistrés dans le propre éditeur de GitTree, préparés et validés dans l'application, et les vérifications du terminal ont été effectuées par rapport à ce que l'application a créé, ou par rapport à des copies distinctes pour l'état avant la mise en scène et pour l'image. Les vérifications Node ont réussi sur le nœud 22, la version déclarée par le projet. Rien n'a été exécuté sur macOS ou Windows, et Ubuntu est le seul système Linux enregistré. L'étage Git inspecté de GitTree est 2.39.0. Une fois que vous êtes connecté à GitTree, Git local est sur le plan gratuit et n'a besoin d'aucun fournisseur ni d'informations d'identification d'API.
Concept
Une différence répond à une question sur two snapshots, et non à savoir si un fichier est « bon ». Un git diff ordinaire compare index → arbre de travail ; git diff --cached compare HEAD → index ; git diff HEAD compare HEAD → arbre de travail. Un plus marque le nouveau texte et un moins marque l'ancien texte, et non l'approbation ou le blâme. Lisez Référence diff de Git pour la sémantique de comparaison.
Un fichier non suivi n'a pas d'entrée d'index ordinaire. Un diff ordinaire vide ne prouve donc pas qu'aucun nouveau fichier n'existe. Vérifiez également l'état et le contenu des fichiers nouvellement ajoutés. Après avoir préparé un nouveau fichier, son ajout complet peut être examiné dans la comparaison mise en cache.
La modification d'Inline/Split/Hunk, d'habillage, de mode image ou d'affichage d'espaces cachés modifie le view, et non les octets du fichier, l'index, les validations ou les références distantes. La mise en scène modifie l'index. Le commit normal final déplace le principal local ; aucun changement d'état du serveur.
Entrées de projet entièrement exécutables
Gardez le fichier README-01 inchangé. Remplacez index.html par HTML-02 et créez tous les fichiers restants ci-dessous avec UTF-8/LF et une nouvelle ligne finale. Le .gitignore minimal est intentionnel : la sortie de build ne doit pas entrer accidentellement dans ce premier commit exécutable. Une politique d’ignorance plus large vient plus tard. Il s'agit de charges utiles de documents de cours, et non de fichiers d'application installés dans le référentiel Web.
index.html — HTML-02
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitTree Features — course demo</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="site-header">
<a href="#main">GitTree Features</a>
</header>
<main id="main">
<section class="hero" aria-labelledby="hero-title">
<p class="eyebrow">GitTree Learn demo project</p>
<h1 id="hero-title">Understand every change.</h1>
<p id="hero-copy">Choose one change, then check what Git saved.</p>
</section>
<section class="features" aria-labelledby="features-title">
<h2 id="features-title">Follow your work</h2>
<div class="cards">
<article class="feature-card">
<h3>History</h3>
<p>Read commits and branches together.</p>
</article>
<article class="feature-card">
<h3>Changes</h3>
<p>Review the exact content selected for a commit.</p>
</article>
<article class="feature-card">
<h3>Recovery</h3>
<p>Inspect the scope of a recovery point before using it.</p>
</article>
</div>
</section>
</main>
<footer><p>Built by a fictional team for GitTree Learn.</p></footer>
<script type="module" src="site.js"></script>
</body>
</html>styles.css — CSS-01
:root {
color-scheme: light;
--page: #f7f8fa;
--surface: #ffffff;
--text: #17212e;
--link: #154ab5;
--line: #bac4d0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--page);
color: var(--text);
font: 1rem/1.6 system-ui, sans-serif;
}
a {
color: var(--link);
}
.site-header,
main,
footer {
width: min(68rem, 100% - 2rem);
margin-inline: auto;
}
.site-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
padding-block: 1rem;
}
.hero {
max-width: 44rem;
padding-block: 3rem;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.2;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: 1rem;
}
.feature-card {
padding: 1rem;
border: 1px solid var(--line);
border-radius: 0.5rem;
background: var(--surface);
}
section {
margin-block-end: 2rem;
}
button,
input {
font: inherit;
}
button,
input {
padding: 0.5rem 0.75rem;
}
:focus-visible {
outline: 3px solid var(--link);
outline-offset: 3px;
}
form {
display: grid;
max-width: 28rem;
gap: 0.5rem;
}site.js — JS-01
export function validateEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
}
if (typeof document !== 'undefined') {
const form = document.querySelector('#signup-form');
const email = document.querySelector('#demo-email');
const result = document.querySelector('#signup-result');
const toggle = document.querySelector('#theme-toggle');
if (form && email && result) {
form.addEventListener('submit', (event) => {
event.preventDefault();
result.textContent = validateEmail(email.value)
? 'Valid format. This demo sent nothing.'
: 'Enter a complete fictional email address.';
});
}
if (toggle) {
toggle.addEventListener('click', () => {
const enabled = document.documentElement.dataset.theme !== 'dark';
document.documentElement.dataset.theme = enabled ? 'dark' : 'light';
toggle.setAttribute('aria-pressed', String(enabled));
toggle.textContent = enabled ? 'Use light mode' : 'Use dark mode';
});
}
}package.json — PAQUET-01
{
"name": "gittree-features",
"version": "0.1.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22"
},
"scripts": {
"lint": "node scripts/lint.mjs",
"test": "node --test",
"build": "node scripts/build.mjs",
"start": "node scripts/serve.mjs"
}
}package-lock.json — LOCK-01
{
"name": "gittree-features",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gittree-features",
"version": "0.1.0",
"engines": {
"node": ">=22"
}
}
}
}tests/site.test.mjs — TEST-01
import assert from 'node:assert/strict';
import test from 'node:test';
import { validateEmail } from '../site.js';
test('accepts the fictional team address', () => {
assert.equal(validateEmail('maya@example.com'), true);
});
test('trims surrounding whitespace', () => {
assert.equal(validateEmail(' leo@example.com '), true);
});
test('rejects a missing domain', () => {
assert.equal(validateEmail('omar@'), false);
});
test('rejects a missing local part', () => {
assert.equal(validateEmail('@example.com'), false);
});
test('rejects spaces inside an address', () => {
assert.equal(validateEmail('sara name@example.com'), false);
});
test('rejects input without an at sign', () => {
assert.equal(validateEmail('priya.example.com'), false);
});scripts/lint.mjs — LINT-01
import { readFile } from 'node:fs/promises';
const files = ['index.html', 'styles.css', 'site.js'];
const problems = [];
for (const file of files) {
const source = await readFile(file, 'utf8');
if (!source.endsWith('\n')) problems.push(file + ': missing final newline');
const lines = source.split('\n');
for (const [index, line] of lines.entries()) {
if (/[\t ]+$/.test(line)) {
problems.push(file + ':' + (index + 1) + ': trailing whitespace');
}
}
}
if (problems.length > 0) {
process.stderr.write(problems.join('\n') + '\n');
process.exitCode = 1;
} else {
process.stdout.write('Source formatting checks passed.\n');
}scripts/build.mjs — CONSTRUIRE-01
import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
const output = path.resolve('dist');
const queue = ['index.html', 'styles.css', 'site.js', 'assets', 'vendor/snippets'];
await mkdir(output, { recursive: true });
while (queue.length > 0) {
const source = queue.pop();
let details;
try {
details = await stat(source);
} catch (error) {
if (error.code === 'ENOENT' && (source === 'assets' || source === 'vendor/snippets')) continue;
throw error;
}
if (details.isDirectory()) {
const entries = await readdir(source, { withFileTypes: true });
for (const entry of entries) {
if (entry.name !== '.git') queue.push(path.join(source, entry.name));
}
continue;
}
const target = path.join(output, source);
await mkdir(path.dirname(target), { recursive: true });
await copyFile(source, target);
}
process.stdout.write('Built the static site in dist/.\n');scripts/serve.mjs — SERVER-01
import { readFile, stat } from 'node:fs/promises';
import http from 'node:http';
import path from 'node:path';
const root = path.resolve('dist');
const port = Number(process.env.PORT ?? 8080);
const types = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.bmp': 'image/bmp',
};
await stat(path.join(root, 'index.html'));
const server = http.createServer(async (request, response) => {
try {
const address = new URL(request.url, 'http://localhost');
const pathname = decodeURIComponent(address.pathname);
const target = path.resolve(root, '.' + (pathname === '/' ? '/index.html' : pathname));
const extension = path.extname(target);
if (!target.startsWith(root + path.sep) || !Object.hasOwn(types, extension)) {
response.writeHead(404).end('Not found');
return;
}
const body = await readFile(target);
response.writeHead(200, {
'Content-Type': types[extension],
'X-Content-Type-Options': 'nosniff',
}).end(body);
} catch {
response.writeHead(404).end('Not found');
}
});
server.listen(port, '127.0.0.1', () => {
process.stdout.write('Demo site: http://127.0.0.1:' + port + '\n');
});
process.on('SIGINT', () => server.close());
process.on('SIGTERM', () => server.close());.gitignore — IGNORER-CONSTRUCTION
dist/Dans GitTree
- Dans le référentiel d'origine, utilisez Command Palette… (Command/Ctrl+Shift+P) → Show Working Changes. Cliquez sur index.html dans Unstaged Files. Avant de le remplacer, identifiez la modification de ligne de support conservée de la leçon 5.
- Utilisez Show Files (Commande/Ctrl+Maj+E) ; focus index.html dans l’arborescence des fichiers et appuyez sur Entrée. Remplacez son contenu de travail par HTML-02, puis Save (Command/Ctrl+S dans l'éditeur). Attendez que l'indicateur non enregistré disparaisse.
- Pour chaque autre fichier complet ci-dessus : palette New File… → Path from the project folder, tapez le chemin relatif exact à la racine → Create, collez son propre corps complet → Save. Les chemins imbriqués créent leurs répertoires manquants. Le refus d'un fichier existant signifie inspecter ce fichier et non écraser le travail inconnu.
- Back to Graph → palette Show Working Changes. Examinez les fichiers non suivis ainsi que index.html. Utilisez Stage File exactement sur les dix chemins modifiés nommés ci-dessus, et non sur la sortie dist générée. Laissez le fichier README existant intact.
- Cliquez sur chaque chemin sous Staged Files. Utilisez Hunk, Inline et Split comme disponibles dans Diff view. Les équivalents de palette sont Show Diff as Hunks, Show Diff Inline, Show Diff Side by Side. Next change est F7 ; Previous change est Maj+F7. L'ancien reste à gauche et le nouveau à droite à Split, même en arabe.
- Inspectez Ignore whitespace / palette Ignore Whitespace in Diffs, puis restaurez-la. Les espaces cachés ne sont pas un contenu absent ; la sélection partielle peut être indisponible lorsque les modifications des espaces sont masquées. N'utilisez pas de filtre d'affichage comme preuve d'un index propre.
- Exécutez les vérifications natives ci-dessous dans le dossier d'origine. Saisissez ensuite le résumé
feat: add styling and local project checks, désactivez amend/push/empty/skip-hooks et choisissez Commit Changes to 10 Files. Commande/Ctrl+Entrée est une soumission alternative, pas une deuxième étape. Vérifiez deux commits et nettoyez le statut de suivi.
Dans la borne
Au point de contrôle input, cette requête en lecture seule capture la modification de la leçon 5 encore non validée :
git diff -- index.htmlSortie complète observée :
diff --git a/index.html b/index.html
index 5470374..27f8718 100644
--- a/index.html
+++ b/index.html
@@ -13,7 +13,7 @@
<section class="hero" aria-labelledby="hero-title">
<p class="eyebrow">GitTree Learn demo project</p>
<h1 id="hero-title">Understand every change.</h1>
- <p id="hero-copy">A clear view of Git history.</p>
+ <p id="hero-copy">Choose one change, then check what Git saved.</p>
</section>
</main>
<footer><p>Built by a fictional team for GitTree Learn.</p></footer>Après avoir enregistré toutes les charges utiles, avant la préparation :
git status --short M index.html
?? .gitignore
?? package-lock.json
?? package.json
?? scripts/
?? site.js
?? styles.css
?? tests/Les entrées de répertoire scripts/ et tests/ sont regroupées avec un statut non suivi, ce qui ne prouve pas que le corps de leur fichier a été examiné.
Effectuer des contrôles ; aucune installation de dépendance ou réseau n'est nécessaire car ces scripts utilisent uniquement les éléments intégrés de Node :
npm run --silent lint
npm test -- --test-reporter=tap
npm run --silent build
git check-ignore dist/index.htmlSortie de peluches observée :
Source formatting checks passed.Observé test-summary excerpt, à l'exclusion du timing par test :
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0Sorties build et ignore observées, une commande chacune :
Built the static site in dist/.
dist/index.htmlLe script de démarrage npm écrit lie le bouclage et sert dist. Cela dure depuis longtemps ; ne confondez pas un processus serveur avec un test de réussite. Son comportement HTTP en direct nécessite toujours une vérification de l'environnement d'enregistrement ; aucune prise de serveur n'est affirmée ici.
Alternative d'étape/commit native, uniquement si l'application n'a pas déjà effectué ces mutations :
git add -- .gitignore index.html styles.css site.js package.json package-lock.json tests/site.test.mjs scripts/lint.mjs scripts/build.mjs scripts/serve.mjs
git diff --cached --stat
git commit --quiet -m "feat: add styling and local project checks"
git rev-list --count HEAD
git status --shortLa validation silencieuse a réussi silencieusement ; count prints 2 et le statut court final est vide. La statistique est une aide à la révision et ne remplace pas la lecture des dix corps de fichiers. dist est ignoré, non validé ; aucune télécommande n'a été ajoutée.
Variante de comparaison d'images séparée
Préparez un copy of this same GitTree Features checkpoint isolé, jamais un deuxième exemple sans rapport ou l'original continu. Ajoutez Assets/diff-card.png avec les octets avant, effectuez un commit clairement étiqueté pour les appareils uniquement, puis remplacez ses octets de travail par après. Aucun fichier image ou média final n'est livré par ces fichiers Markdown.
Les deux PNG ont une moitié gauche bleue. Avant a une moitié droite blanche ; après a un quart vert en bas à droite. Octets complets exacts :
| Version | Dimensions | Octets | SHA-256 |
|---|---|---|---|
| avant | 64 × 64 | 162 | 9a09ba97c890da0d542166ba17882bea8f7bf9b9bd4f5ec068a156f6b7256773 |
| après | 64 × 64 | 171 | 2ddabb4b9859aa53ff9c433a48cb7db0213e184b425a86fef20d8502eed8974f |
before — full PNG Base64 bytes
iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAaUlEQVR4nO3QAQkAAAjAMEtYyr7m0xgi7PACi6yey68LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4D/AAqNletEZdhfWAAAAAElFTkSuQmCCafter — full PNG Base64 bytes
iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAcklEQVR4nO3QQQ0AIBAEsTOBA9QQ1OANfaCC7INOMgZabeyTPF0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ7f14wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfABwAT8dDllEqFx1AAAAAElFTkSuQmCCUn opérateur d'enregistrement peut décoder une charge utile à la fois dans la copie isolée :
node --input-type=module -e "import {mkdirSync,writeFileSync} from 'node:fs'; mkdirSync('assets',{recursive:true}); writeFileSync('assets/diff-card.png',Buffer.from('<selected-full-base64>','base64'));"Remplacez l'espace réservé du support d'angle par une charge utile complète, sans les supports. Décodez avant, préparez/commettez le luminaire, puis décodez après sans mise en scène. Les décomptes/résumés d’octets fournis identifient chaque état ; ne réutilisez jamais une capture d’écran d’image fabriquée.
Dans cette variante, native :
git diff --numstat -- assets/diff-card.pngObservé dans la sonde binaire :
- - assets/diff-card.pngLes tirets signifient que Git rapporte des données binaires, et non aucun pixel modifié.
Dans la vraie application : Show Working Changes → cliquez sur assets/diff-card.png dans Unstaged Files → Side by side → Swipe, déplacez Swipe position avec les touches fléchées → Onion skin, modifiez After image opacity. Équivalents de palette : Show Image Changes Side by Side, Show Image Changes with a Swipe, Show Image Changes as Onion Skin. Les deux côtés doivent réellement décoder ; absent/trop grand/pas une image est un état valide différent, pas une raison pour simuler l'image. La source reconnaît PNG/JPEG/GIF/WebP, not BMP ; le dernier appareil LFS BMP ne doit pas se voir promettre cette vue d'image.
Revenez à l'original, le point de contrôle pour la leçon suivante. Les commits/actifs supplémentaires de la variante d'image n'entrent jamais dans l'historique canonique.
Résultat
Deux commits sur main ; README plus les dix entrées déclarées suivies ; les fichiers d'index/de travail sont conformes à HEAD ; La distribution générée peut exister mais est ignorée. Aucun appareil d'image ni télécommande dans l'arborescence canonique.
Erreurs courantes
- Videz les différences ordinaires pendant que les nouveaux fichiers ne sont pas suivis : inspectez l'état et les ajouts par étapes.
- Lecture de --stat comme étant correct : inspectez le contenu réel et les deux points finaux de comparaison.
- Traiter le masquage des espaces comme la suppression des espaces : ce n'est qu'une vue.
- Validation de dist ou du luminaire côté image : mettez en scène la liste canonique exacte.
Si quelque chose ne va pas
Un échec de lint/test/build est un vrai signal ; conserver sa sortie et corriger uniquement l'entrée de démonstration indiquée. Ne sautez pas les vérifications et ne supprimez pas les dossiers inconnus. Une image bloquée/non prise en charge doit rester un avis binaire/taille honnête jusqu'à ce que son format/taille réel soit réconcilié. Annulez une demande de sélection partielle obsolète et rechargez la comparaison actuelle plutôt que de forcer un correctif.
Vérifiez votre compréhension
Répondez à chaque question, puis lisez pourquoi. Les questions marquées « Obligatoire » comptent pour terminer la leçon.
0 sur 6 corrects
Répondez correctement aux questions requises pour terminer la leçon.
Vérifié dans ce navigateur.
Pratique
À l'original, vérifiez le compte 2, le diff de travail/mis en cache vide et la dist ignorée. Dans la variante d'image séparée, nommez quel quadrant a changé et pourquoi numstat natif rapporte des tirets. Entrée modifiée : si un nouveau fichier reste non suivi au lieu d'être mis en scène, prédisez que le diff ordinaire peut toujours l'omettre.
Votre propre confirmation : le site ne peut pas voir votre ordinateur.
Pour terminer la leçon, répondez correctement aux questions demandées et confirmez l'exercice pratique.
Récapitulatif
- Nommez les deux points de terminaison avant de lire un diff.
- Les vues de texte, d'images binaires et décodées fournissent des preuves différentes.
- Les contrôles et la mise en scène exacte protègent le point de contrôle exécutable.
En rapport
Termes du glossaire introduits
Diff — فروق; morceau — كتلة; index — الفهرس; arbre de travail — مجلد العمل; binaire — ثنائي; non suivi — غير مُتتبَّع.
Transcription vidéo
La narration de la vidéo, repère par repère.
Afficher la transcription
- Lire le comparatif avant de juger du changement.
- La dernière ligne de héros est toujours contenu de travail, pas un nouveau commit.
- Diffs ordinaires et échelonnés comparer différents instantanés.
- Ajoutez les entrées exécutables complètes, en gardant la modification de ligne de support existante.
- De nouveaux fichiers peuvent manquer dans les fichiers ordinaires diff jusqu'à ce qu'ils entrent dans l'index.
- Mettre en scène les entrées déclarées, pas généré de sortie de build.
- La mise en page change votre vue, pas les octets comparés.
- Suivez le texte environnant avant interpréter un ajout ou une suppression.
- Cacher les espaces n'est pas suppression d'un changement d'espace.
- Exécutez le formatage local vérifiez avant de vous engager.
- Les six entrées-validation les tests réussissent dans ce luminaire.
- La sortie de build appartient au répertoire ignoré, pas le commit.
- Valider l'examen projet exécutable localement.
- Vérifiez deux commits et non différence suivie restante.
- Utilisez une copie séparée pour la comparaison des images, pas le point de contrôle continu.
- Les deux charges utiles PNG exactes identifier les états de l’image.
- Comparez les côtés nommés et leurs dimensions réelles.
- Déplacez le diviseur sans changer l'un ou l'autre fichier.
- L'opacité change la comparaison, pas les pixels enregistrés.
- Le résumé binaire de Git n'est pas un nombre de pixels modifiés.
- Un fichier non pris en charge ou surdimensionné doit garder son avis honnête.
- Gardez le luminaire d'image en dehors de l'histoire canonique.
- Prédire quelle comparaison affiche un fichier récemment mis en scène.
- Ensuite, sélectionnez des morceaux et des lignes sans abandonner d'autres travaux.