From ee3370cfa5cb17261f722c501c94edf0a431f91d Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 29 Jan 2020 18:56:47 -0500 Subject: Resurrected Git documents --- docs/git/config.md | 22 ++++ docs/git/detached-sigs.md | 172 ++++++++++++++++++++++++++++++ docs/git/index.md | 14 +++ docs/git/pull-request-workflow.md | 91 ++++++++++++++++ docs/git/scratch.md | 55 ++++++++++ docs/git/stop-using-git-pull-to-deploy.md | 98 +++++++++++++++++ docs/git/survival.md | 81 ++++++++++++++ 7 files changed, 533 insertions(+) create mode 100644 docs/git/config.md create mode 100644 docs/git/detached-sigs.md create mode 100644 docs/git/index.md create mode 100644 docs/git/pull-request-workflow.md create mode 100644 docs/git/scratch.md create mode 100644 docs/git/stop-using-git-pull-to-deploy.md create mode 100644 docs/git/survival.md (limited to 'docs/git') diff --git a/docs/git/config.md b/docs/git/config.md new file mode 100644 index 0000000..84b7ae3 --- /dev/null +++ b/docs/git/config.md @@ -0,0 +1,22 @@ +# git-config Settings You Want + +Git comes with some fairly [lkml](http://www.tux.org/lkml/)-specific configuration defaults. You should fix this. All of the items below can be set either for your entire login account (`git config --global`) or for a specific repository (`git config`). + +Full documentation is under `git help config`, unless otherwise stated. + +* `git config user.name 'Your Full Name'` and `git config user.email 'your-email@example.com'`, obviously. Git will remind you about this if you forget. + +* `git config merge.defaultToUpstream true` - causes an unqualified `git merge` to merge the current branch's configured upstream branch, rather than being an error. This makes `git merge` much more consistent with `git rebase`, and as the two tools fill very similar workflow niches, it's nice to have them behave similarly. + +* `git config rebase.autosquash true` - causes `git rebase -i` to parse magic comments created by `git commit --squash=some-hash` and `git commit --fixup=some-hash` and reorder the commit list before presenting it for further editing. See the descriptions of “squash” and “fixup” in `git help rebase` for details; autosquash makes amending commits other than the most recent easier and less error-prone. + +* `git config branch.autosetupmerge always` - newly-created branches whose start point is a branch (`git checkout master -b some-feature`, `git branch some-feature origin/develop`, and so on) will be configured to have the start point branch as their upstream. By default (with `true` rather than `always`) this only happens when the start point is a remote-tracking branch. + +* `git config rerere.enabled true` - enable “reuse recorded resolution.” The `git help rerere` docs explain it pretty well, but the short version is that git can record how you resolve conflicts during a “test” merge and reuse the same approach when resolving the same conflict later, in a “real” merge. + +## For advanced users + +A few things are nice when you're getting started, but become annoying when +you no longer need them. + +* `git config advice.detachedHead` - if you already understand the difference between having a branch checked out and having a commit checked out, and already understand what “detatched head” means, the warning on every `git checkout ...some detatched thing...` isn't helping anyone. This is also useful repositories used for deployment, where specific commits (from tags, for example) are regularly checked out. diff --git a/docs/git/detached-sigs.md b/docs/git/detached-sigs.md new file mode 100644 index 0000000..0b8a386 --- /dev/null +++ b/docs/git/detached-sigs.md @@ -0,0 +1,172 @@ +# Notes Towards Detached Signatures in Git + +Git supports a limited form of object authentication: specific object categories in Git's internal model can have GPG signatures embedded in them, allowing the authorship of the objects to be verified using GPG's underlying trust model. Tag signatures can be used to verify the authenticity and integrity of the _snapshot associated with a tag_, and the authenticity of the tag itself, filling a niche broadly similar to code signing in binary distribution systems. Commit signatures can be used to verify the authenticity of the _snapshot associated with the commit_, and the authorship of the commit itself. (Conventionally, commit signatures are assumed to also authenticate either the entire line of history leading to a commit, or the diff between the commit and its first parent, or both.) + +Git's existing system has some tradeoffs. + +* Signatures are embedded within the objects they sign. The signature is part of the object's identity; since Git is content-addressed, this means that an object can neither be retroactively signed nor retroactively stripped of its signature without modifying the object's identity. Git's distributed model means that these sorts of identity changes are both complicated and easily detected. + +* Commit signatures are second-class citizens. They're a relatively recent addition to the Git suite, and both the implementation and the social conventions around them continue to evolve. + +* Only some objects can be signed. While Git has relatively weak rules about workflow, the signature system assumes you're using one of Git's more widespread workflows by limiting your options to at most one signature, and by restricting signatures to tags and commits (leaving out blobs, trees, and refs). + +I believe it would be useful from an authentication standpoint to add "detached" signatures to Git, to allow users to make these tradeoffs differently if desired. These signatures would be stored as separate (blob) objects in a dedicated `refs` namespace, supporting retroactive signatures, multiple signatures for a given object, "policy" signatures, and authentication of arbitrary objects. + +The following notes are partially guided by Git's one existing "detached metadata" facility, `git notes`. Similarities are intentional; divergences will be noted where appropriate. Detached signatures are meant to interoperate with existing Git workflow as much as possible: in particular, they can be fetched and pushed like any other bit of Git metadata. + +A detached signature cryptographically binds three facts together into an assertion whose authenticity can be checked by anyone with access to the signatory's keys: + +1. An object (in the Git sense; a commit, tag, tree, or blob), +2. A policy label, and +3. A signatory (a person or agent making the assertion). + +These assertions can be published separately from or in tandem with the objects they apply to. + +## Policies + +Taking a hint from Monotone, every signature includes a "policy" identifying how the signature is meant to be interpreted. Policies are arbitrary strings; their meaning is entirely defined by tooling and convention, not by this draft. + +This draft uses a single policy, `author`, for its examples. A signature under the `author` policy implies that the signatory had a hand in the authorship of the designated object. (This is compatible with existing interpretations of signed tags and commits.) (Authorship under this model is strictly self-attested: you can claim authorship of anything, and you cannot assert anyone else's authorship.) + +The Monotone documentation suggests a number of other useful policies related to testing and release status, automated build results, and numerous other factors. Use your imagination. + +## What's In A Signature + +Detached signatures cover the disk representation of an object, as given by + +```bash +git cat-file +``` + +For most of Git's object types, this means that the signed content is plain text. For `tree` objects, the signed content is the awful binary representation of the tree, _not_ the pretty representation given by `git ls-tree` or `git show`. + +Detached signatures include the "policy" identifier in the signed content, to prevent others from tampering with policy choices via `refs` hackery. (This will make more sense momentarily.) The policy identifier is prepended to the signed content, terminated by a zero byte (as with Git's own type identifiers, but without a length field as length checks are performed by signing and again when the signature is stored in Git). + +To generate the _complete_ signable version of an object, use something equivalent to the following shell snippet: + +```bash +# generate-signable POLICY TYPE SHA1 +function generate-signable() { + printf '%s\0' "$1" + git cat-file "$2" "$3" +} +``` + +(In the process of writing this, I discovered how hard it is to get Unix's C-derived shell tools to emit a zero byte.) + +## Signature Storage and Naming + +We assume that a userid will sign an object at most once. + +Each signature is stored in an independent blob object in the repository it applies to. The signature object (described above) is stored in Git, and its hash recorded in `refs/signatures///`. + +```bash +# sign POLICY TYPE SHA1 FINGERPRINT +function sign() { + local SIG_HASH=$( + generate-signable "$@" | + gpg --batch --no-tty --sign -u "$4" | + git hash-object --stdin -w -t blob + ) + git update-ref "refs/signatures/$1/$3/$4" +} +``` + +Stored signatures always use the complete fingerprint to identify keys, to minimize the risk of colliding key IDs while avoiding the need to store full keys in the `refs` naming hierarchy. + +The policy name can be reliably extracted from the ref, as the trailing part has a fixed length (in both path segments and bytes) and each ref begins with a fixed, constant prefix `refs/signatures/`. + +## Signature Verification + +Given a signature ref as described above, we can verify and authenticate the signature and bind it to the associated object and policy by performing the following check: + +1. Pick apart the ref into policy, SHA1, and key fingerprint parts. +2. Reconstruct the signed body as above, using the policy name extracted from the ref. +3. Retrieve the signature from the ref and combine it with the object itself. +4. Verify that the policy in the stored signature matches the policy in the ref. +5. Verify the signature with GPG: + + ```bash + # verify-gpg POLICY TYPE SHA1 FINGERPRINT + verify-gpg() { + { + git cat-file "$2" "$3" + git cat-file "refs/signatures/$1/$3/$4" + } | gpg --batch --no-tty --verify + } + ``` + +6. Verify the key fingerprint of the signing key matches the key fingerprint in the ref itself. + +The specific rules for verifying the signature in GPG are left up to the user to define; for example, some sites may want to auto-retrieve keys and use a web of trust from some known roots to determine which keys are trusted, while others may wish to maintain a specific, known keyring containing all signing keys for each policy, and skip the web of trust entirely. This can be accomplished via `git-config`, given some work, and via `gpg.conf`. + +## Distributing Signatures + +Since each signature is stored in a separate ref, and since signatures are _not_ expected to be amended once published, the following refspec can be used with `git fetch` and `git push` to distribute signatures: + +``` +refs/signatures/*:refs/signatures/* +``` + +Note the lack of a `+` decoration; we explicitly do not want to auto-replace modified signatures, normally; explicit user action should be required. + +## Workflow Notes + +There are two verification workflows for signatures: "static" verification, where the repository itself already contains all the refs and objects needed for signature verification, and "pre-receive" verification, where an object and its associated signature may be being uploaded at the same time. + +_It is impractical to verify signatures on the fly from an `update` hook_. Only `pre-receive` hooks can usefully accept or reject ref changes depending on whether the push contains a signature for the pushed objects. (Git does not provide a good mechanism for ensuring that signature objects are pushed before their subjects.) Correctly verifying object signatures during `pre-receive` regardless of ref order is far too complicated to summarize here. + +## Attacks + +### Lies of Omission + +It's trivial to hide signatures by deleting the signature refs. Similarly, anyone with access to a repository can delete any or all detached signatures from it without otherwise invalidating the signed objects. + +Since signatures are mostly static, sites following the recommended no-force policy for signature publication should only be affected if relatively recent signatures are deleted. Older signatures should be available in one or more of the repository users' loca repositories; once created, a signature can be legitimately obtained from anywhere, not only from the original signatory. + +The signature naming protocol is designed to resist most other forms of assertion tampering, but straight-up omission is hard to prevent. + +### Unwarranted Certification + +The `policy` system allows any signatory to assert any policy. While centralized signature distribution points such as "release" repositories can make meaningful decisions about which signatures they choose to accept, publish, and propagate, there's no way to determine after the fact whether a policy assertion was obtained from a legitimate source or a malicious one with no grounds for asserting the policy. + +For example, I could, right now, sign an `all-tests-pass` policy assertion for the Linux kernel. While there's no chance on Earth that the LKML team would propagate that assertion, if I can convince you to fetch signatures from my repository, you will fetch my bogus assertion. If `all-tests-pass` is a meaningful policy assertion for the Linux kernel, then you will have very few options besides believing that I assert that all tests have passed. + +### Ambigiuous Policy + +This is an ongoing problem with crypto policy systems and user interfaces generally, but this design does _nothing_ to ensure that policies are interpreted uniformly by all participants in a repository. In particular, there's no mechanism described for distributing either prose or programmatic policy definitions and checks. All policy information is out of band. + +Git already has ambiguity problems around commit signing: there are multiple ways to interpret a signature on a commit: + +1. I assert that this snapshot and commit message were authored as described in this commit's metadata. (In this interpretation, the signature's authenticity guarantees do _not_ transitively apply to parents.) + +2. I assert that this snapshot and commit message were authored as described in this commit's metadata, based on exactly the parent commits described. (In this interpretation, the signature's authenticity guarantees _do_ transitively apply to parents. This is the interpretation favoured by XXX LINK HERE XXX.) + +3. I assert that this _diff_ and commit message was authored as described in this commit's metadata. (No assertions about the _snapshot_ are made whatsoever, and assertions about parentage are barely sensical at all. This meshes with widespread, diff-oriented policies.) + +### Grafts and Replacements + +Git permits post-hoc replacement of arbitrary objects via both the grafts system (via an untracked, non-distributed file in `.git`, though some repositories distribute graft lists for end-users to manually apply) and the replacements system (via `refs/replace/`, which can optionally be fetched or pushed). The interaction between these two systems and signature verification needs to be _very_ closely considered; I've not yet done so. + +Cases of note: + +* Neither signature nor subject replaced - the "normal" case +* Signature not replaced, subject replaced (by graft, by replacement, by both) +* Signature replaced, subject not replaced +* Both signature and subject replaced + +It's tempting to outright disable `git replace` during signing and verification, but this will have surprising effects when signing a ref-ish instead of a bare hash. Since this is the _normal_ case, I think this merits more thought. (I'm also not aware of a way to disable grafts without modifying `.git`, and having the two replacement mechanisms treated differently may be dangerous.) + +### No Signed Refs + +I mentioned early in this draft that Git's existing signing system doesn't support signing refs themselves; since refs are an important piece of Git's workflow ecosystem, this may be a major omission. Unfortunately, this proposal doesn't address that. + +## Possible Refinements + +* Monotone's certificate system is key+value based, rather than label-based. This might be useful; while small pools of related values can be asserted using mutually exclusive policy labels (whose mutual exclusion is a matter of local interpretation), larger pools of related values rapidly become impractical under the proposed system. + + For example, this proposal would be inappropriate for directly asserting third-party authorship; the asserted author would have to appear in the policy name itself, exposing the user to a potentially very large number of similar policy labels. + +* Ref signing via a manifest (a tree constellation whose paths are ref names and whose blobs sign the refs' values). Consider cribbing DNSSEC here for things like lightweight absence assertions, too. + +* Describe how this should interact with commit-duplicating and commit-rewriting workflows. diff --git a/docs/git/index.md b/docs/git/index.md new file mode 100644 index 0000000..5cfc4d8 --- /dev/null +++ b/docs/git/index.md @@ -0,0 +1,14 @@ +# Collected Advice about Git + +* [git-config Settings You Want](config.md) — Git is highly configurable, and the defaults have gotten drastically better over the years, but there are still some non-default behaviours that I've found make life better. + +* [Notes Towards Detached Signatures in Git](detached-sigs.md) — An idea I had, but never fully developed, for implementing after-the-fact object signing on top of Git. This was based on a similar feature in Monotone, which I'd found very effective for annotating commits on the fly. + +* [Life With Pull Requests](pull-request-workflow.md) — Some notes I made while getting up to speed with pull requests to help my team come to grips with the workflows. + +* [Git Is Not Magic](scratch.md) — An exploration of Git's on-disk data structures and the design choices taken very early in Git's existence. + +* [Stop using `git pull` for deployment!](stop-using-git-pull-to-deploy.md) — Describing the least-painful way to use Git as a deployment tool I had worked out, circa 2014. Written in an aversarial style as a response to repeated ”why don't we just”s that, while well-intentioned, came from an incomplete understanding of what `git pull` does. + +* [Git Survival Guide](survival.md) — Some words of caution about Git, `git`'s preferred workflows, and various recoverable mistakes. + diff --git a/docs/git/pull-request-workflow.md b/docs/git/pull-request-workflow.md new file mode 100644 index 0000000..2d3e2c0 --- /dev/null +++ b/docs/git/pull-request-workflow.md @@ -0,0 +1,91 @@ +# Life With Pull Requests + +I've been party to a number of discussions with folks contributing to pull-request-based projects on Github (and other hosts, but mostly Github). Because of Git's innate flexibility, there are lots of ways to work with pull requests. Here's mine. + +I use a couple of naming conventions here that are not stock `git`: + +* `origin` is the repository to which you _publish_ proposed changes, and + +* `upstream` is the repository from which you receive ongoing development, and + which will receive your changes if they are accepted. + +## One-time setup + +Do these things once, when starting out on a project. Keep the results around for later. + +I'll be referring to the original project repository as `upstream` and pretending its push URL is `UPSTREAM-URL` below. In real life, the URL will often be something like `git@github.com:someguy/project.git`. + +### Fork the project + +Use the repo manager's forking tool to create a copy of the project in your own namespace. This generally creates your copy with a bunch of useless tat; feel free to ignore all of this, as the only purpose of this copy is to provide somewhere for _you_ to publish _your_ changes. + +We'll be calling this repository `origin` later. Assume it has a URL, which I'll abbreviate `ORIGIN-URL`, for `git push` to use. + +(You can leave this step for later, but if you know you're going to do it, why not get it out of the way?) + +### Clone the project and configure it + +You'll need a clone locally to do work in. Create one from `origin`: + +```bash +git clone ORIGIN-URL some-local-name +``` + +While you're here, `cd` into it and add the original project as a remote: + +```bash +cd some-local-name +git remote add upstream UPSTREAM-URL +``` + +## Feature process + +Do these things for each feature you work on. To switch features, just use `git checkout my-feature`. + +### Create a new feature branch locally + +We use `upstream`'s `master` branch here, so that your feature includes all of `upstream`'s state initially. We also need to make sure our local cache of `upstream`'s state is correct: + +```bash +git fetch upstream +git checkout upstream/master -b my-feature +``` + +### Do work + +If you need my help here, stop now. + +### Integrate upstream changes + +If you find yourself needing something that's been added upstream, use _rebase_ to integrate it to avoid littering your feature branch with “meaningless” merge commits. + +```bash +git checkout my-feature +git fetch upstream +git rebase upstream/master +``` + +### Publish your branch + +When you're “done,” publish your branch to your personal repository: + +```bash +git push origin my-feature +``` + +Then visit your copy in your repo manager's web UI and create a pull request for `my-feature`. + +### Integrating feedback + +Very likely, your proposed changes will need work. If you use history-editing to integrate feedback, you will need to use `--force` when updating the branch: + +```bash +git push --force origin my-feature +``` + +This is safe provided two things are true: + +1. **The branch has not yet been merged to the upstream repo.** +2. You are only force-pushing to your fork, not to the upstream repo. + +Generally, no other users will have work based on your pull request, so force-pushing history won't cause problems. diff --git a/docs/git/scratch.md b/docs/git/scratch.md new file mode 100644 index 0000000..e912a1e --- /dev/null +++ b/docs/git/scratch.md @@ -0,0 +1,55 @@ +# Git Is Not Magic + +I'm bored. Let's make a git repository out of whole cloth. + +Git repos are stored in .git: + + fakegit$ mkdir .git + +They have a “symbolic ref” (which are text files, see [`man +git-symbolic-ref`](http://jk.gs/git-symbolic-ref.html)) named `HEAD`, pointing +to the currently checked-out branch. Let's use `master`. Branches are refs +under `refs/heads` (see [`man git-branch`](http://jk.gs/git-branch.html)): + + fakegit ((unknown))$ echo 'ref: refs/heads/master' > .git/HEAD + +The have an object database and a refs database, both of which are simple +directories (see [`man +gitrepository-layout`](http://jk.gs/gitrepository-layout.html) and [`man +gitrevisions`](http://jk.gs/gitrevisions.html)). Let's also enable the reflog, +because it's a great safety net if you use history-editing tools in git: + + fakegit ((ref: re...))$ mkdir .git/refs .git/objects .git/logs + fakegit (master #)$ + +Now `__git_ps1`, at least, is convinced that we have a working git repository. +Does it work? + + fakegit (master #)$ echo 'Hello, world!' > hello.txt + fakegit (master #)$ git add hello.txt + fakegit (master #)$ git commit -m 'Initial commit' + [master (root-commit) 975307b] Initial commit + 1 file changed, 1 insertion(+) + create mode 100644 hello.txt + + fakegit (master)$ git log + commit 975307ba0485bff92e295e3379a952aff013c688 + Author: Owen Jacobson + Date: Wed Feb 6 10:07:07 2013 -0500 + + Initial commit + +[Eeyup](https://www.youtube.com/watch?v=3VwVpaWUu30). + +----- + +Should you do this? **Of course not.** Anywhere you could run these commands, +you could instead run `git init` or `git clone`, which set up a number of +other structures, including `.git/config` and any unusual permissions options. +The key part here is that a directory's identity as “a git repository” is +entirely a function of its contents, not of having been blessed into being by +`git` itself. + +You can infer a lot from this: for example, you can infer that it's “safe” to +move git repositories around using FS tools, or to back them up with the same +tools, for example. This is not as obvious to everyone as you might hope; people diff --git a/docs/git/stop-using-git-pull-to-deploy.md b/docs/git/stop-using-git-pull-to-deploy.md new file mode 100644 index 0000000..078c95b --- /dev/null +++ b/docs/git/stop-using-git-pull-to-deploy.md @@ -0,0 +1,98 @@ +# Stop using `git pull` for deployment! + +## The problem + +* You have a Git repository containing your project. +* You want to “deploy” that code when it changes. +* You'd rather not download the entire project from scratch for each + deployment. + +## The antipattern + +“I know, I'll use `git pull` in my deployment script!” + +Stop doing this. Stop teaching other people to do this. It's wrong, and it +will eventually lead to deploying something you didn't want. + +Deployment should be based on predictable, known versions of your code. +Ideally, every deployable version has a tag (and you deploy exactly that tag), +but even less formal processes, where you deploy a branch tip, should still be +deploying exactly the code designated for release. `git pull`, however, can +introduce new commits. + +`git pull` is a two-step process: + +1. Fetch the current branch's designated upstream remote, to obtain all of the + remote's new commits. +2. Merge the current branch's designated upstream branch into the current + branch. + +The merge commit means the actual deployed tree might _not_ be identical to +the intended deployment tree. Local changes (intentional or otherwise) will be +preserved (and merged) into the deployment, for example; once this happens, +the actual deployed commit will _never_ match the intended commit. + +`git pull` will approximate the right thing “by accident”: if the current +local branch (generally `master`) for people using `git pull` is always clean, +and always tracks the desired deployment branch, then `git pull` will update +to the intended commit exactly. This is pretty fragile, though; many git +commands can cause the local branch to diverge from its upstream branch, and +once that happens, `git pull` will always create new commits. You can patch +around the fragility a bit using the `--ff-only` option, but that only tells +you when your deployment environment has diverged and doesn't fix it. + +## The right pattern + +Quoting [Sitaram Chamarty](http://gitolite.com/the-list-and-irc/deploy.html): + +> Here's what we expect from a deployment tool. Note the rule numbers -- +> we'll be referring to some of them simply by number later. +> +> 1. All files in the branch being deployed should be copied to the +> deployment directory. +> +> 2. Files that were deleted in the git repo since the last deployment +> should get deleted from the deployment directory. +> +> 3. Any changes to tracked files in the deployment directory after the +> last deployment should be ignored when following rules 1 and 2. +> +> However, sometimes you might want to detect such changes and abort if +> you found any. +> +> 4. Untracked files in the deploy directory should be left alone. +> +> Again, some people might want to detect this and abort the deployment. + +Sitaram's own documentation talks about how to accomplish these when +“deploying” straight out of a bare repository. That's unwise (not to mention +impractical) in most cases; deployment should use a dedicated clone of the +canonical repository. + +I also disagree with point 3, preferring to keep deployment-related changes +outside of tracked files. This makes it much easier to argue that the changes +introduced to configure the project for deployment do not introduce new bugs +or other surprise features. + +My deployment process, given a dedicated clone at `$DEPLOY_TREE`, is as +follows: + + cd "${DEPLOY_TREE}" + git fetch --all + git checkout --force "${TARGET}" + # Following two lines only required if you use submodules + git submodule sync + git submodule update --init --recursive + # Follow with actual deployment steps (run fabric/capistrano/make/etc) + +`$TARGET` is either a tag name (`v1.2.1`) or a remote branch name +(`origin/master`), but could also be a commit hash or anything else Git +recognizes as a revision. This will detach the head of the `$DEPLOY_TREE` +repository, which is fine as no new changes should be authored in this +repository (so the local branches are irrelevant). The warning Git emits when +`HEAD` becomes detached is unimportant in this case. + +The tracked contents of `$DEPLOY_TREE` will end up identical to the desired +commit, discarding local changes. The pattern above is very similar to what +most continuous integration servers use when building from Git repositories, +for much the same reason. diff --git a/docs/git/survival.md b/docs/git/survival.md new file mode 100644 index 0000000..60d1b62 --- /dev/null +++ b/docs/git/survival.md @@ -0,0 +1,81 @@ +# Git Survival Guide + +I think the `git` UI is pretty awful, and encourages using Git in ways that +will screw you. Here are a few things I've picked up that have saved my bacon. + +* You will inevitably need to understand Git's “internals” to make use of it + as an SCM tool. Accept this early. If you think your SCM tool should not + expose you to so much plumbing, [don't](http://mercurial.selenic.com) + [use](http://bazaar.canonical.com) [Git](http://subversion.apache.org). + * Git weenies will claim that this plumbing is what gives Git all of its + extra power. This is true; it gives Git the power to get you out of + situations you wouldn't be in without Git. +* `git log --graph --decorate --oneline --color --all` +* Run `git fetch` habitually. Stale remote-tracking branches lead to sadness. +* `git push` and `git pull` are **not symmetric**. `git push`'s + opposite operation is `git fetch`. (`git pull` is equivalent to `git fetch` + followed by `git merge`, more or less). +* [Git configuration values don't always have the best defaults](config). +* The upstream branch of `foo` is `foo@{u}`. The upstream branch of your + checked-out branch is `HEAD@{u}` or `@{u}`. This is documented in `git help + revisions`. +* You probably don't want to use a merge operation (such as `git pull`) to + integrate upstream changes into topic branches. The resulting history can be + very confusing to follow, especially if you integrate upstream changes + frequently. + * You can leave topic branches “real” relatively safely. You can do + a test merge to see if they still work cleanly post-integration without + actually integrating upstream into the branch permanently. + * You can use `git rebase` or `git pull --rebase` to transplant your + branch to a new, more recent starting point that includes the changes + you want to integrate. This makes the upstream changes a permanent part + of your branch, just like `git merge` or `git pull` would, but generates + an easier-to-follow history. Conflict resolution will happen as normal. +* Example test merge, using `origin/master` as the upstream branch and `foo` + as the candidate for integration: + + git fetch origin + git checkout origin/master -b test-merge-foo + git merge foo + # run tests, examine files + git diff origin/master..HEAD + + To discard the test merge, delete the branch after checking out some other + branch: + + git checkout foo + git branch -D test-merge-foo + + You can combine this with `git rerere` to save time resolving conflicts in + a later “real,” permanent merge. + +* You can use `git checkout -p` to build new, tidy commits out of a branch + laden with “wip” commits: + + git fetch + git checkout $(git merge-base origin/master foo) -b foo-cleaner-history + git checkout -p foo -- paths/to/files + # pick out changes from the presented patch that form a coherent commit + # repeat 'git checkout -p foo --' steps for related files to build up + # the new commit + git commit + # repeat 'git checkout -p foo --' and 'git commit' steps until no diffs remain + + * Gotcha: `git checkout -p` will do nothing for files that are being + created. Use `git checkout`, instead, and edit the file if necessary. + Thanks, Git. + * Gotcha: The new, clean branch must diverge from its upstream branch + (`origin/master`, in the example above) at exactly the same point, or + the diffs presented by `git checkout -p foo` will include chunks that + revert changes on the upstream branch since the “dirty” branch was + created. The easiest way to find this point is with `git merge-base`. + +## Useful Resources + +That is, resoures that can help you solve problems or understand things, not +resources that reiterate the man pages for you. + +* Sitaram Chamarty's [git concepts + simplified](http://sitaramc.github.com/gcs/) +* Tv's [Git for Computer + Scientists](http://eagain.net/articles/git-for-computer-scientists) -- cgit v1.2.3