Notes to self for using Git.

1. Delete a remote branch

Since Git 1.7: git push origin --delete <branchName>

Previous to that version: git push origin :branch-to-delete. This shows the power of the git push syntax (git push repository refspec), that often seems disguised in a simpler form, where the refspec is just a local branch name.

2. Stashing parts of the changes

git stash save --patch sucks with complex changes. Say I want to create a stash for later of some small changes, and ideally I’ll want to stash what is staged. That would probably be git stash --staged. The --keep-index option excludes what is staged in the index, and --no-keep-index merges the staged and unstaged changes. This sucks, so the alternative is:

  1. Stage what I want to stash.

  2. git stash save --keep-index 'temporary stash'

  3. git stash save 'what I really wanted to stash'

  4. git stash pop stash@{1}

This was asked by other people, e.g. in Proposal for git stash : add --staged option. There the following option was offered:

I often have the same problem. How about doing this:

git add config_real.xml # [Add the reverse of what you wanted to stash]
git stash -k
git reset

The difference between our approaches is that I keep thinking of the staging area as the place to put changes I want to keep, not that I want to forget for a moment.

This way it’s one step less, but it requires you to think in reverse, and stage the oposite of what you wanted to put in the stash. That’s bad because I normally want to put in the stash less than what I want to keep, and it’s less intuitive for me.

3. Reading content from a different branch to the working copy

Example: you get your own personal configuration files or notes for the project in a personal branch in the repository, which doesn’t contain code at all, so it’s not going to clash with your the normal branch:

$ git read-tree unrelated-files
$ git checkout-index -a

(maybe missing git reset?)

Example 2: checkout some files from a different branch to inspect them locally:

$ git read-tree other-branch
$ git checkout-index --prefix other/ -- somefile.txt

4. Manual work tree with GIT_DIR and GIT_WORK_TREE

The use case is something like the mentioned in the previous point (see the subsection below for an easier implementation with the technique shown here), but also a much harder to tackle use case that would not scale without something easier to use. The tree should look like the following:

game
game/content
game/other-game-content
game/some-game-3rd-party-modification
game/other-game-3rd-party-modification

In a nutshell, that is:

  • The contents of the game are in a game directory.

  • A 3rd party project (a game modification, a.k.a. "mod") needs to be copied to the root of the game directory.

  • Same with many other mods.

The game doesn’t come under version control (thought it could be, but it would be us doing so for not much gain if at all). The modifications are most of the time put in repositories by their authors, and their tree looks like:

.gitignore
README
setup-foo.exe
foo
foo/code
foo/translations
foo/docs
foo/...

Files like setup-foo.exe are going to be different from project to project, so are not a concern. But the top-level README, .gitignore, etc., are a problem as are going to clash if we want more than one mod, which is the point here. We want to ignore those files on the root, but we want to checkout the foo subdirectory of the project, and run different git commands affecting it.

I happen to have this project’s repository already cloned on a different place. I used to just copy the directory I’m interested in to the game one. But what do we need to make it via Git?

We only need to set GIT_DIR. Really, just that (ok, setting GIT_WORK_TREE is not strictly necessary but it would be very inconvenient not to do so, see later). Setting GIT_DIR will tell git which repository we are interested in. We point it to one of the already checked out mods in the directory outside the game installation one.

Example:

cd ~/game
export GIT_DIR=~/projects/mods/mod-a/.git
export GIT_WORK_TREE=$PWD

With that set, now git status will print the same as if we were in ~/projects/mods/mod-a (that’s what GIT_DIR is doing), but with the project contents where wiped out from the working directory (because we are in a different place, so the files are not found there). If we don’t change directory again, we don’t need to set GIT_WORK_TREE to a different value because it’s assumed to be the working directory, so it’s already $PWD. If we want to move around the tree, better that we set it to the root of the game, like in the previous snippet.

Now we can checkout the contents of that repository that we care about:

git checkout subdirectory

Now running git status still shows removed files, the ones not in the subdirectory (e.g. a README):

On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    .gitignore
        deleted:    README

no changes added to commit (use "git add" and/or "git commit -a")

That is surprisingly easy to fix. There are two flags that can be set (and later unset) in each of those files: "skip worktree" and "assume unchanged". Those are set using git update-index, and the manual page advises against them in a few situations where it can be avoided. There is a worthy read in Stack Overflow comparing the two. The risks of using it for our use case seem as close to zero as possible, as we are just removing it and not caring at all about its contents. So let’s do it:

git update-index --skip-worktree README
git update-index --skip-worktree .gitignore

To revert and remove the flag:

git update-index --no-skip-worktree README
git update-index --no-skip-worktree .gitignore

To check which ones we added, in case we need to know which ones to revert:

git ls-files -v | grep '^S'
Note
The subsection explaining this in an easier way is pending to be written. But a taste of it would be checking this blog article I wrote at the company blog: https://www.vikingsoftware.com/blog/multiple-git-repositories-in-the-same-tree/

5. Git LFS

  • git lfs dedup: deduplicates (if supported, see git lfs dedup --test) files in the working tree from the object storage. Requires btrfs or other file system with support for it.

  • A "file URL" is now supported as remote, so one could push from one repo on a PC to one in a different storage (e.g. external hard drive), and the LFS objects would be transferred as well.

  • git bundle support for an offline workflow. This is a ticket which was filled asking an offline or local-only approach, but people did not have everything too clear. Points where this was implemented: Add support for local paths and Allow literal local paths as remotes.

6. repo

8. Pending to review again and take further notes/summarize

9. Useful demo to peek at internals with a scratch repo

$ mkdir /tmp/demo
$ cd /tmp/demo
$ git init
master #$    # Note the prompt

The prompt comes from a file in the git package:

$ dpkg -S /usr/lib/git-core/git-sh-prompt
git: /usr/lib/git-core/git-sh-prompt`
$ rm -r .git/hooks
$ tree -a
.
└── .git
    ├── HEAD
    ├── branches
    ├── config
    ├── description
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
        ├── heads
        └── tags

9 directories, 4 files
$ echo hi > greet.txt
$ git hash-object greet.txt
45b983be36b73c0788dc9cbcb76cbb80fc7bb057
$ sha1sum greet.txt
55ca6286e3e4f4fba5d0448333fa99fc5a404a73  greet.txt
$ echo -ne "blob 3\0hi\n" | sha1sum
45b983be36b73c0788dc9cbcb76cbb80fc7bb057  -
$ echo -ne "blob `cat greet.txt|wc -c`\0" | cat - greet.txt | sha1sum
45b983be36b73c0788dc9cbcb76cbb80fc7bb057  -

$ # The tree inside ".git" is exactly the same so far. But let's add something.
# TODO: either git hash-object -w or git add and show the effect

10. Torvalds vs Github