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:
-
Stage what I want to stash.
-
git stash save --keep-index 'temporary stash'
-
git stash save 'what I really wanted to stash'
-
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 resetThe 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, seegit 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
-
https://www.bitleaks.net/blog/fork-android-on-github/ Pretty much the only place where I’ve found "documentation" on how one is supposed to start working on a project based on Google’s awful
repo
tool.
8. Pending to review again and take further notes/summarize
-
https://github.com/newren/git-filter-repo A better git-filter-branch?
-
https://github.com/Osse/git-talk-impls I still have to review it, but could be a complement to what I’ve already done to play with internals. Note that there is also some C++ code, so interesting to look at it as well.
-
https://www.atlassian.com/git/tutorials/ (Good advanced tips section)
-
http://haacked.com/archive/2014/02/21/reviewing-merge-commits/
-
https://hackernoon.com/whats-new-in-git-2-11-64860aea6c4f#.okeijpsd4
-
https://hackernoon.com/be-125-more-efficient-with-git-60556a1ce971#.spu2hg82n
-
https://www.atlassian.com/git/tutorials/git-subtree or https://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/ (old URL)
-
http://xaizek.github.io/2014-06-22/make-tabs-visible-in-git-diff/
-
https://www.kernel.org/pub/software/scm/git/docs/howto/revert-a-faulty-merge.html
-
https://github.com/acaudwell/Gource OpenGL visualization of the repo history
-
https://reversed.top/2014-06-22/make-tabs-visible-in-git-diff/
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
-
https://github.com/torvalds/linux/pull/11 When Torvalds ranted how bad PRs are, and with links to a conversation on a Subsurface PR (https://github.com/subsurface/subsurface/pull/18 being the updated link).
-
https://github.com/torvalds/linux/pull/7 A prank PR
-
https://github.com/torvalds/linux/pull/8 A noisy PR