Skip to content

Commit

Permalink
Fix the submodule bug!
Browse files Browse the repository at this point in the history
I think this bug was the longest standing bug in any of my projects, ever. I opened this bug report (#229) over 4 years ago!

It's really not that complicated of a fix, either. I'm disappointed that this took me so long to fix.

 `shallow-backup` will only support a nested `dotfiles` repo. If it exists, prompt the user to make a commit in the submodule first to avoid a failed commit on the parent repo later.

Fix #229
  • Loading branch information
alichtman committed Oct 9, 2023
1 parent dd2712e commit 7d9730b
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 27 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ Options:
--reinstall-fonts Reinstall fonts.
--reinstall-packages Reinstall packages.
--remote TEXT Set remote URL for the git repo.
--separate-dotfiles-repo Use if you are trying to maintain a separate
dotfiles repo and running into issue #229.

--show Display config file.
-v, --version Display version and author info.
Expand All @@ -112,9 +110,9 @@ This backup tool is git-integrated, meaning that you can easily store your backu

_If you choose to back up to a public repository, look at every file you're backing up to make sure you want it to be public._

**What if I'd like to maintain a separate repo for my dotfiles?**
**How can I maintain a separate repo for my dotfiles?**

`shallow-backup` makes this easy! After making your first backup, `cd` into the `dotfiles/` directory and run `$ git init`. Create a `.gitignore` and a new repo on your favorite version control platform. This repo will be maintained independently (manually) of the base `shallow-backup` repo. Note that you may need to use the `-separate_dotfiles_repo` flag to get this to work, and it may [break some other functionality of the tool](https://github.com/alichtman/shallow-backup/issues/229). It's ok for my use case, though.
`shallow-backup` makes this easy! After making your first backup, `cd` into the `dotfiles/` directory and run `$ git init`. Create a `.gitignore`, and a create / set up (link the upstream remote, etc) a new repo on your favorite version control platform. With operations involving the parent `shallow-backup` repo, `shallow-backup` will prompt you interactively to update the nested submodule. After that is taken care of, `shallow-backup` will move on to updating the parent. The `dotfiles` repo will be tracked as a submodule.

Here's a `bash` script that I wrote to [automate my dotfile backup workflow](https://github.com/alichtman/scripts/blob/master/backup-and-update-dotfiles.sh). You can use this by placing it in your `$PATH`, making it executable, and running it.

Expand Down
15 changes: 6 additions & 9 deletions shallow_backup/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,6 @@
"--reinstall-packages", is_flag=True, default=False, help="Reinstall packages."
)
@click.option("--remote", default=None, help="Set remote URL for the git repo.")
@click.option(
"--separate-dotfiles-repo",
is_flag=True,
default=False,
help="Use if you are trying to maintain a separate dotfiles repo and running into issue #229.",
)
@click.option("--show", is_flag=True, default=False, help="Display config file.")
@click.option(
"--version",
Expand Down Expand Up @@ -121,7 +115,6 @@ def cli(
reinstall_fonts,
reinstall_packages,
remote,
separate_dotfiles_repo,
show,
version,
):
Expand Down Expand Up @@ -252,8 +245,10 @@ def cli(
git_add_all_commit_push(repo, "full_backup")
elif backup_dots_flag:
backup_dotfiles(dotfiles_path, dry_run=dry_run, skip=True)
# The reason that dotfiles/.git is special cased, and none of the others are is because maintaining a separate git repo for dotfiles is a common use case.
handle_separate_git_dir_in_dotfiles(dotfiles_path, dry_run)
if not dry_run:
git_add_all_commit_push(repo, "dotfiles", separate_dotfiles_repo)
git_add_all_commit_push(repo, "dotfiles")
elif backup_configs_flag:
backup_configs(configs_path, dry_run=dry_run, skip=True)
if not dry_run:
Expand All @@ -273,10 +268,12 @@ def cli(
if action == "back up":
if target == "all":
backup_all(dotfiles_path, packages_path, fonts_path, configs_path)
handle_separate_git_dir_in_dotfiles(dotfiles_path, dry_run=dry_run)
git_add_all_commit_push(repo, target)
elif target == "dotfiles":
backup_dotfiles(dotfiles_path)
git_add_all_commit_push(repo, target, separate_dotfiles_repo)
handle_separate_git_dir_in_dotfiles(dotfiles_path, dry_run)
git_add_all_commit_push(repo, target)
elif target == "configs":
backup_configs(configs_path)
git_add_all_commit_push(repo, target)
Expand Down
57 changes: 43 additions & 14 deletions shallow_backup/git_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from pathlib import Path
import sys
import git
from git import GitCommandError
Expand Down Expand Up @@ -64,7 +65,7 @@ def create_gitignore(dir_path, key):
f.write("{}\n".format(ignore))


def safe_git_init(dir_path):
def safe_git_init(dir_path: str) -> (git.Repo, bool):
"""
If there is no git repo inside the dir_path, intialize one.
Returns tuple of (git.Repo, bool new_git_repo_created)
Expand All @@ -83,34 +84,63 @@ def safe_git_init(dir_path):
return repo, False


def git_add_all_commit_push(repo, message, separate_dotfiles_repo=False):
def handle_separate_git_dir_in_dotfiles(dotfiles_path: Path, dry_run: bool = False):
print_yellow_bold("Checking for separate git repo in dotfiles directory...")
if ".git" in os.listdir(dotfiles_path):
dotfiles_repo = git.Repo(dotfiles_path)
if dotfiles_repo.is_dirty():
print_green_bold("Detected a nested dotfiles repo that is dirty!!")
print_green_bold(
"Do you want to create and push a commit in this repo first, before dealing with the parent?"
)
if prompt_yes_no(
"If you do not, the parent repo will not be able to commit the dotfile changes (due to a dirty submodule)",
Fore.YELLOW,
):
print_green_bold("Okay, switching into dotfiles subrepo...")
git_add_all_commit_push(
dotfiles_repo, message="dotfiles", dry_run=dry_run
)
print_green_bold("Switching back to parent shallow-backup repo...")


def prompt_to_show_git_diff(repo):
if prompt_yes_no("Show git diff?", Fore.BLUE):
print(repo.git.diff(staged=True, color="always"))


def git_add_all_and_print_status(repo):
print_yellow("Staging all files for commit...")
repo.git.add(all=True)
print(repo.git.status())
prompt_to_show_git_diff(repo)


def git_add_all_commit_push(repo: git.Repo, message: str, dry_run: bool = False):
"""
Stages all changed files in dir_path and its children folders for commit,
commits them and pushes to a remote if it's configured.
:param git.repo repo: The repo
:param str message: The commit message
:param bool separate_dotfiles_repo: Flag for denoting a workflow where git submodules are used to maintain a separate repo for just dotfiles.
"""
if separate_dotfiles_repo:
print_yellow_bold("Skipping commit to avoid git submodule error.")
print_yellow_bold(
"Issue tracked at: https://github.com/alichtman/shallow-backup/issues/229"
)
return

if repo.index.diff(None) or repo.untracked_files:
if repo.is_dirty():
git_add_all_and_print_status(repo)
if not prompt_yes_no("Make a commit? Ctrl-C to exit", Fore.BLUE):
return
if dry_run:
print_yellow_bold("Dry run: Would have made a commit!")
return
print_yellow_bold("Making new commit...")
repo.git.add(A=True)
try:
repo.git.commit(m=COMMIT_MSG[message])
# Git submodule issue https://github.com/alichtman/shallow-backup/issues/229
except git.exc.GitCommandError as e:
error = e.stdout.strip()
error = error[error.find("'") + 1 : -1]
print_red_bold(f"ERROR on Commit: {e.command}\n{error}\n")
print_red_bold(
"Issue tracked at: https://github.com/alichtman/shallow-backup/issues/229"
"Please open a new issue at https://github.com/alichtman/shallow-backup/issues/new"
)
return

Expand All @@ -121,7 +151,6 @@ def git_add_all_commit_push(repo, message, separate_dotfiles_repo=False):
"Pushing to remote:",
f"{repo.remotes.origin.url}[origin/{repo.active_branch.name}]...",
)

repo.git.fetch()
repo.git.push("--set-upstream", "origin", "HEAD")
else:
Expand Down

0 comments on commit 7d9730b

Please sign in to comment.