
2 min read
Back Up Your Site With a Git Branch
Before deleting my entire site for the rebuild, I made a one-command backup that keeps the old code browsable forever. Here's the five-minute git trick.
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(reference('tags')).default([]),
draft: z.boolean().default(false),
summary: z.string().optional(),
related: z.array(reference('blog')).default([]),
}),
});
export async function getPublishedPosts(): Promise<Post[]> {
const posts = await getCollection('blog', ({ data }) =>
import.meta.env.DEV ? true : !data.draft,
);
return posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
}
/** Whole minutes at ~200 words per minute, never less than 1. */
export function readingTime(post: Post): number {
const words = (post.body ?? '').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(words / 200));
}// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;
// Apply the theme before first paint, and again after every
// ClientRouter swap (the new document arrives without the class).
const apply = () => {
const stored = localStorage.getItem('theme');
const dark = stored
? stored === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', dark);
};
apply();
document.addEventListener('astro:after-swap', apply);
export const person = buildPiece({
'@id': ids.person,
'@type': 'Person',
name: AUTHOR.name,
url: `${SITE.url}/`,
jobTitle: AUTHOR.occupation,
sameAs: Object.values(SOCIALS),
});
export const postUrl = (post: Post) =>
`${SITE.url}/blog/${post.id}/`;Posts about git, branches, and version control workflows.

· 2 min read
Before deleting my entire site for the rebuild, I made a one-command backup that keeps the old code browsable forever. Here's the five-minute git trick.