diff --git a/src/main.rs b/src/main.rs
@@ -223,6 +223,25 @@ struct Readme {
   format:  ReadmeFormat,
 }
 
+#[derive(Debug, Clone)]
+// this is necessary for pages to link to the correct addresses when rendering
+// private repos
+/// A path describing the root in the output directory tree
+enum RootPath {
+  Slash,
+  Path(String),
+}
+
+impl Display for RootPath {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    if let Self::Path(p) = self {
+      write!(f, "/{}", Escaped(p.trim_matches('/')))?;
+    }
+
+    Ok(())
+  }
+}
+
 struct RepoRenderer<'repo> {
   pub name:        String,
   pub description: Option<String>,
@@ -235,6 +254,8 @@ struct RepoRenderer<'repo> {
   pub license:     Option<String>,
   pub last_commit: Option<SystemTime>,
 
+  pub full_build:  bool,
+  pub output_root: &'repo RootPath,
   pub output_path: PathBuf,
 
 }
@@ -246,7 +267,12 @@ enum RenderResult {
 }
 
 impl<'repo> RepoRenderer<'repo> {
-  fn new<P>(repo: RepoInfo, output_path: P) -> Result<Self, ()>
+  fn new<P>(
+    repo: RepoInfo,
+    output_path: P,
+    full_build: bool,
+    output_root: &'repo RootPath,
+  ) -> Result<Self, ()>
   where
     P: AsRef<Path> + AsRef<OsStr>,
   {
@@ -341,27 +367,31 @@ impl<'repo> RepoRenderer<'repo> {
       license,
       output_path: PathBuf::from(&output_path),
       last_commit,
+      full_build,
+      output_root,
     })
   }
 
   pub fn render(&self) -> io::Result<RenderResult> {
-    // TODO: [feature]: disable this via a flag
     // skip rendering the repo if the last modification of its pages is
     // older than the last commit
-    if let Some(repo_last_commit) = self.last_commit {
-      let mut repo_output_path = PathBuf::from(&self.output_path);
-      repo_output_path.push(&self.name);
-
-      if let Ok(meta) = fs::metadata(&repo_output_path) {
-        let output_last_modified = meta.modified().unwrap();
-
-        if output_last_modified > repo_last_commit {
-          return Ok(RenderResult::Skipped);
+    if !self.full_build {
+      if let Some(repo_last_commit) = self.last_commit {
+        let mut repo_output_path = PathBuf::from(&self.output_path);
+        repo_output_path.push(&self.name);
+
+        // TODO: this is incorrect: meta.modified() gives us the date the
+        // directory was created at, not the date it was last updated
+        if let Ok(meta) = fs::metadata(&repo_output_path) {
+          let output_last_modified = meta.modified().unwrap();
+
+          if output_last_modified > repo_last_commit {
+            return Ok(RenderResult::Skipped);
+          }
         }
       }
     }
 
-
     self.render_summary()?;
     let last_commit_time = self.render_log()?;
     if let Some(ref license) = self.license {
@@ -386,17 +416,21 @@ impl<'repo> RepoRenderer<'repo> {
     }
     writeln!(f, "<nav>")?;
     writeln!(f, "<ul>")?;
-    writeln!(f, "<li{class}><a href=\"/{name}/index.html\">summary</a></li>",
+    writeln!(f, "<li{class}><a href=\"{root}/{name}/index.html\">summary</a></li>",
+                root = self.output_root,
                 name = Escaped(&self.name),
                 class = if matches!(title, PageTitle::Summary { .. }) { " class=\"nav-selected\"" } else { "" })?;
-    writeln!(f, "<li{class}><a href=\"/{name}/{COMMIT_SUBDIR}/index.html\">log</a></li>",
+    writeln!(f, "<li{class}><a href=\"{root}/{name}/{COMMIT_SUBDIR}/index.html\">log</a></li>",
+                root = self.output_root,
                 name = Escaped(&self.name),
                 class = if matches!(title, PageTitle::Log { .. } | PageTitle::Commit { .. }) { " class=\"nav-selected\"" } else { "" })?;
-    writeln!(f, "<li{class}><a href=\"/{name}/{TREE_SUBDIR}/index.html\">tree</a></li>",
+    writeln!(f, "<li{class}><a href=\"{root}/{name}/{TREE_SUBDIR}/index.html\">tree</a></li>",
+                root = self.output_root,
                 name = Escaped(&self.name),
                 class = if matches!(title, PageTitle::TreeEntry { .. }) { " class=\"nav-selected\"" } else { "" })?;
     if self.license.is_some() {
-      writeln!(f, "<li{class}><a href=\"/{name}/license.html\">license</a></li>",
+      writeln!(f, "<li{class}><a href=\"{root}/{name}/license.html\">license</a></li>",
+                  root = self.output_root,
                   name = Escaped(&self.name),
                   class = if matches!(title, PageTitle::License { .. }) { " class=\"nav-selected\"" } else { "" })?;
     }
@@ -519,7 +553,8 @@ impl<'repo> RepoRenderer<'repo> {
 
           writeln!(
             &mut f,
-            "<tr><td><a href=\"/{name}/{TREE_SUBDIR}/{path}.html\">{path}</a></td></tr>",
+            "<tr><td><a href=\"{root}/{name}/{TREE_SUBDIR}/{path}.html\">{path}</a></td></tr>",
+            root = self.output_root,
             name = Escaped(&self.name),
             path = Escaped(&path.to_string_lossy()),
           )?;
@@ -541,7 +576,8 @@ impl<'repo> RepoRenderer<'repo> {
 
           writeln!(
             &mut f,
-            "<tr><td><a href=\"/{name}/{TREE_SUBDIR}/{path}/index.html\" class=\"subtree\">{path}/</a></td></tr>",
+            "<tr><td><a href=\"{root}/{name}/{TREE_SUBDIR}/{path}/index.html\" class=\"subtree\">{path}/</a></td></tr>",
+            root = self.output_root,
             name = Escaped(&self.name),
             path = Escaped(&path.to_string_lossy()),
           )?;
@@ -601,13 +637,14 @@ impl<'repo> RepoRenderer<'repo> {
     page_path.extend(&path);
     let page_path = format!("{}.html", page_path.to_string_lossy());
 
-    // TODO: [feature]: disable this via a flag
     // skip rendering the page if the commit the blob was last updated on is
     // older than the page
-    if let Ok(meta) = fs::metadata(&page_path) {
-      let last_modified = meta.modified().unwrap();
-      if last_modified > last_commit_time[&blob.id()] {
-        return Ok(());
+    if !self.full_build {
+      if let Ok(meta) = fs::metadata(&page_path) {
+        let last_modified = meta.modified().unwrap();
+        if last_modified > last_commit_time[&blob.id()] {
+          return Ok(());
+        }
       }
     }
 
@@ -641,7 +678,8 @@ impl<'repo> RepoRenderer<'repo> {
     writeln!(&mut f, "<td align=\"right\"></td>")?;
     writeln!(&mut f, "</tr>")?;
     writeln!(&mut f, "<tr>")?;
-    writeln!(&mut f, "<td><a href=\"/{name}/{BLOB_SUBDIR}/{path}\">{path}</a></td>",
+    writeln!(&mut f, "<td><a href=\"{root}/{name}/{BLOB_SUBDIR}/{path}\">{path}</a></td>",
+                     root = self.output_root,
                      name = Escaped(&self.name),
                      path = Escaped(&path.to_string_lossy()))?;
     // TODO: print the size differently for larger blobs?
@@ -741,8 +779,12 @@ impl<'repo> RepoRenderer<'repo> {
 
       writeln!(&mut f, "<article>")?;
       writeln!(&mut f, "<div>")?;
-      writeln!(&mut f, "<span class=\"commit-heading\"><a href=\"/{name}/{COMMIT_SUBDIR}/{id}.html\">{shorthand_id}</a> — {author}</span>",
-                       name = Escaped(&self.name))?;
+      writeln!(
+        &mut f,
+        "<span class=\"commit-heading\"><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{shorthand_id}</a> — {author}</span>",
+        root = self.output_root,
+        name = Escaped(&self.name),
+      )?;
       writeln!(&mut f, "<time datetime=\"{datetime}\">{date}</time>",
                        datetime  = DateTime(time), date = Date(time))?;
       writeln!(&mut f, "</div>")?;
@@ -870,9 +912,8 @@ impl<'repo> RepoRenderer<'repo> {
     path.push(COMMIT_SUBDIR);
     path.push(format!("{}.html", commit.id()));
 
-    // TODO: [feature]: add a flag to ignore this
     // skip rendering the commit page if the file already exists
-    if path.exists() {
+    if !self.full_build && path.exists() {
       return Ok(());
     }
 
@@ -897,14 +938,16 @@ impl<'repo> RepoRenderer<'repo> {
     writeln!(&mut f, "<dl>")?;
 
     writeln!(&mut f, "<dt>Commit</dt>")?;
-    writeln!(&mut f, "<dd><a href=\"/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>",
+    writeln!(&mut f, "<dd><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>",
+                     root = self.output_root,
                      name = Escaped(&self.name), id = commit.id())?;
 
     if let Ok(ref parent) = commit.parent(0) {
       writeln!(&mut f, "<dt>Parent</dt>")?;
       writeln!(
         &mut f,
-        "<dd><a href=\"/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>",
+        "<dd><a href=\"{root}/{name}/{COMMIT_SUBDIR}/{id}.html\">{id}<a/><dd>",
+        root = self.output_root,
         name = Escaped(&self.name),
         id = parent.id()
       )?;
@@ -1005,7 +1048,8 @@ impl<'repo> RepoRenderer<'repo> {
         Delta::Added => {
           writeln!(
             &mut f,
-            "<pre><b>diff --git /dev/null b/<a href=\"/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>",
+            "<pre><b>diff --git /dev/null b/<a href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>",
+            root = self.output_root,
             name = Escaped(&self.name),
             new_path = delta_info.new_path.to_string_lossy(),
           )?;
@@ -1020,7 +1064,8 @@ impl<'repo> RepoRenderer<'repo> {
         _ => {
           writeln!(
             &mut f,
-            "<pre><b>diff --git a/<a id=\"d#{delta_id}\" href=\"/{name}/{TREE_SUBDIR}/{new_path}.html\">{old_path}</a> b/<a href=\"/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>",
+            "<pre><b>diff --git a/<a id=\"d#{delta_id}\" href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{old_path}</a> b/<a href=\"{root}/{name}/{TREE_SUBDIR}/{new_path}.html\">{new_path}</a></b>",
+            root = self.output_root,
             name = Escaped(&self.name),
             new_path = delta_info.new_path.to_string_lossy(),
             old_path = delta_info.old_path.to_string_lossy(),
@@ -1354,6 +1399,7 @@ fn render_footer(f: &mut File) -> io::Result<()> {
 fn render_index<P : AsRef<Path> + AsRef<OsStr>>(
   output_path: P,
   repos: &[RepoInfo],
+  output_root: &RootPath,
 ) -> io::Result<()> {
   let mut path = PathBuf::from(&output_path);
   path.push("index.html");
@@ -1375,7 +1421,8 @@ fn render_index + AsRef>(
     writeln!(&mut f, "<article>")?;
 
     writeln!(&mut f, "<h4>")?;
-    writeln!(&mut f, "<a href=\"/{repo}/index.html\">{repo}</a>",
+    writeln!(&mut f, "<a href=\"{root}/{repo}/index.html\">{repo}</a>",
+                     root = output_root,
                      repo = Escaped(&repo.name))?;
     writeln!(&mut f, "</h4>")?;
 
@@ -1406,45 +1453,73 @@ fn render_index + AsRef>(
 }
 
 #[derive(Clone, Debug)]
-enum SubCommand {
+struct Cmd {
+  sub_cmd: SubCmd,
+
+  full_build:  bool,
+  output_root: RootPath,
+}
+
+#[derive(Clone, Debug)]
+enum SubCmd {
   RenderBatch {
-    batch_path: String,
+    batch_path:  String,
     output_path: String,
   },
   Render {
-    repo_path: String,
+    repo_name:   String,
+    parent_path: String,
     output_path: String,
   },
 }
 
-impl SubCommand {
+impl Cmd {
   pub fn parse() -> Result<(Self, String), ()> {
     let mut args = env::args();
 
     #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-    enum Tag {
+    enum CmdTag {
       RenderBatch,
       Render,
     }
 
     let program_name = args.next().unwrap();
 
-    let tag = match args.next() {
-      Some(s) if s == "render-batch" => Tag::RenderBatch,
-      Some(s) if s == "render"       => Tag::Render,
-      Some(s) => {
-        errorln!("Unknown subcommand {s:?}");
-        log::usage(&program_name);
-        return Err(());
-      }
-      None => {
-        errorln!("No subcommand provided");
-        log::usage(&program_name);
-        return Err(());
+    let mut full_build = false;
+    let mut output_root = RootPath::Slash;
+
+    let cmd = loop {
+      match args.next() {
+        Some(arg) if arg == "render-batch" => break CmdTag::RenderBatch,
+        Some(arg) if arg == "render"       => break CmdTag::Render,
+
+        Some(arg) if arg == "-B" => {
+          full_build = true;
+        }
+        Some(arg) if arg == "--output-root" => {
+          if let Some(root) = args.next() {
+            output_root = RootPath::Path(root);
+          } else {
+            errorln!("No value provided for the `--output-root` flag");
+            log::usage(&program_name);
+            return Err(());
+          }
+        }
+
+        Some(arg) => {
+          errorln!("Unknown flag {arg:?}");
+          log::usage(&program_name);
+          return Err(());
+        }
+        None => {
+          errorln!("No subcommand provided");
+          log::usage(&program_name);
+          return Err(());
+        }
       }
     };
 
-    let mut input_path = if let Some(dir) = args.next() {
+    let input_path = if let Some(dir) = args.next() {
       dir
     } else {
       errorln!("No input path provided");
@@ -1452,23 +1527,10 @@ impl SubCommand {
       return Err(());
     };
 
-    if tag == Tag::Render {
-      // input_path should be an absolute path because we later want to extract
-      // the parent and file name
-      input_path = match fs::canonicalize(&input_path) {
-        Ok(path) => path.to_string_lossy().to_string(),
-        Err(e) => {
-          errorln!("Could not extract absolute path from {input_path:?}: {e}");
-          return Err(());
-        }
-      };
-    }
-
     let output_path = if let Some(dir) = args.next() {
       dir
     } else {
       // TODO: make this message better
-      // TODO: print USAGE
       errorln!("No output path provided");
       log::usage(&program_name);
       return Err(());
@@ -1479,22 +1541,44 @@ impl SubCommand {
       log::usage(&program_name);
     }
 
-    match tag {
-      Tag::RenderBatch => Ok((
-        Self::RenderBatch { batch_path: input_path, output_path, },
-        program_name
-      )),
-      Tag::Render => Ok((
-        Self::Render { repo_path: input_path, output_path, },
-        program_name
-      )),
-    }
+    let sub_cmd = match cmd {
+      CmdTag::RenderBatch => {
+        SubCmd::RenderBatch { batch_path: input_path, output_path, }
+      }
+      CmdTag::Render => {
+        let input_abs = match fs::canonicalize(&input_path) {
+          Ok(path) => path,
+          Err(e) => {
+            errorln!("Could not extract absolute path from {input_path:?}: {e}");
+            return Err(());
+          }
+        };
+
+        let parent_path = if let Some(parent) = input_abs.parent() {
+          parent.to_string_lossy().to_string()
+        } else {
+          errorln!("Could not extract parent path from {input_path:?}");
+          return Err(());
+        };
+
+        let repo_name = if let Some(name) = input_abs.file_name() {
+          name.to_string_lossy().to_string()
+        } else {
+          errorln!("Could not extract repository name from {input_path:?}");
+          return Err(());
+        };
+
+        SubCmd::Render { parent_path, repo_name, output_path, }
+      }
+    };
+
+    Ok((Self { sub_cmd, full_build, output_root, }, program_name))
   }
 }
 
 fn main() -> ExitCode {
   #[allow(unused_variables)]
-  let (cmd, program_name) = if let Ok(cmd) = SubCommand::parse() {
+  let (cmd, program_name) = if let Ok(cmd) = Cmd::parse() {
     cmd
   } else {
     return ExitCode::FAILURE;
@@ -1516,8 +1600,8 @@ fn main() -> ExitCode {
     }
   }
 
-  match cmd {
-    SubCommand::RenderBatch { batch_path, output_path } => {
+  match cmd.sub_cmd {
+    SubCmd::RenderBatch { batch_path, output_path } => {
       let repos = if let Ok(rs) = RepoInfo::from_batch_path(&batch_path) {
         rs
       } else {
@@ -1525,7 +1609,7 @@ fn main() -> ExitCode {
       };
 
       info!("Updating global repository index...");
-      if let Err(e) = render_index(&output_path, &repos) {
+      if let Err(e) = render_index(&output_path, &repos, &cmd.output_root) {
         errorln!("Failed rendering global repository index: {e}");
       }
       info_done!();
@@ -1535,7 +1619,14 @@ fn main() -> ExitCode {
       for (i, repo) in repos.into_iter().enumerate() {
         info_count!(i + 1, n_repos; "{name}...", name = repo.name);
 
-        let renderer = if let Ok(r) = RepoRenderer::new(repo, &output_path) {
+        let renderer = RepoRenderer::new(
+          repo,
+          &output_path,
+          cmd.full_build,
+          &cmd.output_root,
+        );
+
+        let renderer = if let Ok(r) = renderer {
           r
         } else {
           return ExitCode::FAILURE;
@@ -1552,24 +1643,7 @@ fn main() -> ExitCode {
 
       }
     }
-    SubCommand::Render { repo_path, output_path } => {
-      let repo_path = Path::new(&repo_path);
-
-      // TODO: get absolute path beforehand?
-      let parent_path = if let Some(parent) = repo_path.parent() {
-        parent
-      } else {
-        errorln!("Could not extract parent path from {repo_path:?}");
-        return ExitCode::FAILURE;
-      };
-
-      let repo_name = if let Some(name) = repo_path.file_name() {
-        name
-      } else {
-        errorln!("Could not extract repository name from {repo_path:?}");
-        return ExitCode::FAILURE;
-      };
-
+    SubCmd::Render { parent_path, repo_name, output_path } => {
       let repos = if let Ok(rs) = RepoInfo::from_batch_path(parent_path) {
         rs
       } else {
@@ -1577,7 +1651,7 @@ fn main() -> ExitCode {
       };
 
       info!("Updating global repository index...");
-      if let Err(e) = render_index(&output_path, &repos) {
+      if let Err(e) = render_index(&output_path, &repos, &cmd.output_root) {
         errorln!("Failed rendering global repository index: {e}");
       }
       info_done!();
@@ -1589,7 +1663,14 @@ fn main() -> ExitCode {
 
         info!("Updating pages for {name:?}...", name = repo.name);
 
-        let renderer = if let Ok(r) = RepoRenderer::new(repo, &output_path) {
+        let renderer = RepoRenderer::new(
+          repo,
+          &output_path,
+          cmd.full_build,
+          &cmd.output_root,
+        );
+
+        let renderer = if let Ok(r) = renderer {
           r
         } else {
           return ExitCode::FAILURE;