Skip to content

Instantly share code, notes, and snippets.

@creativecoder
Created April 17, 2014 23:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save creativecoder/11016995 to your computer and use it in GitHub Desktop.
Save creativecoder/11016995 to your computer and use it in GitHub Desktop.
git version control notes

Git Version Control

  • Understanding git

    • Anatomy of git
      • HEAD
        • Think of it as the snapshot of your last commit
        • Pointer to the current branch reference, which is the last commit you made or the last commit that was checked out
      • Index
        • Snapshot of your next commit
        • Includes all of the files from your previous commit plus the changes you've staged
      • Working directory
        • These are the actual files on your filesystem
  • Configuration

    • --version - lists the version of the git install
    • Update from within git using: git clone http://github.com/git/git.git (run as sudo for to update for all users)
    • Config file comes in 3 flavors:
      • --global
        • stored in ~/.gitconfig; specific to user
      • --system - stored in /etc/gitconfig and applies to every user and all their repositories
      • --local - stored in .git/config within each repository directory and applies only to the repository
    • Check the config file: config --list
    • Can also use config {key}, like config user.name to check a specific key
    • Set user name and email for recording commits
      • config --global user.name "Name"
      • config --global user.email "email@domain.com"
    • To use a different text editor (uses the default system editor, usually Vi or Vim: config --global core.editor emacs (will use the emacs editor) or nano
    • To use a different diff tool: config --global merge.tool vimdiff (will use vimdiff)
    • Set up aliases
      • config --global alias.[alias-name] [git-command] will setup git to trigger the specified command using the alias
      • Ex: git config --global alias.co checkout would trigger a checkout command when get co is typed
      • Ex: git config --global alias.unstage 'reset HEAD --' makes it easier to upstage a file
      • Ex: git config --global alias.last 'log -1 HEAD' allows you to just see the last commit
      • Note that if you want to trigger an external command with an alias, prefix it with !; Ex: git config --global alias.visual "!gitk"
    • Enable autocompletion
      • git-completion.bash file located in the contrib/completion directory in source code
      • To enable for current user
        • Copy file to home directory
        • Add "source ~/.git-completion.bash to your .bashrc file
      • To enable for all users
        • Copy file to /opt/local/etc/bash_completion.d directory
      • Alternatively, create the following two files in your home directory
        • .bash_profile - "source ~/.bashrc"
        • .bashrc - "source /usr/local/git/contrib/completion/git-completion.bash"
      • Press the tab key when writing a git command to see suggestions
    • Fix permission issues
      • Set get to ignore file permission changes: git config core.filemode false
    • diff/difftool
    • Help for any git command help <verb>, <verb> --help, or git-<verb>
  • Creating Repositories and Managing Files

    • init
      • Use init by itself within an existing directory to convert that directory into a git repository
      • Create a new repository in a new directory with init directoryName
    • add * or add [filename]
      • For untracked files: adds to the repository to be tracked by git
      • For tracked files: stages file to be included in the next commit
      • Note that adding a file stages it in the state its in when it's added. If you modify the file further, those changes won't be staged until you add it again
      • add . or add folder/ adds the current directory and everything under it (but doesn't handle deleted files)
      • Wildcard searches can be done with \\* (need to escape the * character)
      • add -u adds all file changes for files that are already tracked, including deleted files
      • add -A does both add . and add -u, when you want to add new files and remove deleted files
    • status - tells which branch you are on and status of files (tracked, changed, staged, etc)
    • diff 
      • Tells what you've changed but not yet staged
      • diff --staged (or --cached) - tells what you've staged buy haven't yet committed
    • a. rm [filename]
      • Stages the file for removal; at the next commit, the file will no longer be tracked
      • If you simply remove the file without the git command, it will show up as changed but not updated (aka untagged)
      • -f is used to remove a modified file already added to the index (this overrides the safety feature that prevents you removing a file that hasn't been recorded in the repository yet)
      • --cached will remove the file from being tracked, but keep it in the directory (useful if you forgot to add something to .gitignore
      • file-glob patterns like
        • git rm log/\\*.log - removes all files with the .log extension in the log/ directory
        • git rm /*~ - removes all files that end with ~
    • mv [oldfilename][newfilename]
      • Used to rename a file, but not really needed, because git will figure out that you renamed files
      • Equivalent to running these 3 commands
        • mv oldfilename newfilename
        • git rm newfilename
        • git add newfilename
    • reset [filename] unstages a file
    • checkout -- [filename] will revert the file to its state at the previous commit (and all changes are GONE FOREVER)
    • stash - stores changes you don't yet want to commit
      • Stores stashes in a stack, with the most recent on top
        • --keep-index will only stash changes that have not been added to the index with git add
      • stash list will list all stashes made
      • stash apply applies the most recent stash
        • stash apply stash${n} applies the specified stash
        • Stashes are not deleted when applied
        • Stashes can be applied on a different branch than where the they were made
        • Stashes can be applied on a branch with uncommitted changes
        • Use --index with stash apply if your stash contains changes to a file that is already staged and you want the stashed changes to be staged as well
      • stash drop [stash-name] to delete a stash
      • stash pop [stash-name] to apply and delete a stash at the same time
      • Un-apply a stash: git stash show-p [stash-name] | git apply -R (assumes the most recent stash if you don't supply a stash name)
      • Create  a new branch from a stash: git stash branch [branch-name]
        • Creates a new branch, checks out the commit you were on when you created the stash, reapplies the stash there, and drops the stash if it applies successfully
        • Then you can merge the branch back into your main working branch when you're ready
      • Create a stash-unapply command: git config --global alias.stash-unapply '!git stash show -p | git apply -R'
    • .gitignore file contains rules for excluding files from the repository
      • Blank lines or lines starting with # are ignored within the file
      • Standard glob patterns work
        • * matches zero or more characters
        • [abc] matches any character inside the brackets
        • ? matches a single character
        • [0-9] matches any character within this range
        • Use a \ to escape special characters (like # and !)
      • Paths
        • End patterns with / to specify a directory
        • * within a path will not match any directory, only the final filename
        • A leading / matches the beginning of the path name
          • /*.html matches index.html but not /home/index.html
        • A leading **/ means match an in any directory
        • Trailing /* means match anything within a directory
        • foo/**/bar means match 0 or more directories inside foo that contain bar
      • Negate patterns by starting it with !
    • update-index --assume-unchanged
      • Ignore uncommitted changes in a file that is already tracked
    • git ls-files lists all of the files currently being tracked in a repo
  • Remotes

    • clone - create a working copy of a repository
      • Automatically adds the remote repository as origin
      • From local to local: clone /path
      • From remote
        • clone git://url/file.git (uses the git protocol; can also use http or https)
        • clone user@server:/path/file.git will use ssh
        • Folder name: by default the folder name is the same as the cloned repository; you can also specify a folder name after the path to the file
    • remote lists the shortnames of each remote handle you've specified
      • origin is the default name of a server you've cloned
      • ssh remotes are the only kind you can push to
      • -v shows the URL associated with each shortname
    • remote add [remote-name] [url]
      • Will assign a shortname to the repository at the specified url
      • Then use the shortname to reference the url with any git command
      • [shortname]/master will access the master branch
      • remote add origin <server> - connect existing repository with remote server and set that server as origin
    • remote show [remote-name]
      • Lists the URL for the remote repository and tracking branch information and all remote references that have been pulled
      • Shows which branch is automatically pushed when a push is executed, "new remote branches" (branches on the remote you don't have locally yet), "stale tracking branches" (remote branches you have that have been removed from the remote server), and tracked remote branches (that are automatically merged when pull is executed)
    • remote rename [filename] [newfilename] renames a remote reference
    • remote rm removes a remote reference
    • remote update fetches from all remote repositories
    • Remote tracking branches
      • branch -r lists all remote tracking branches
        • -a lists all branches
      • fetch [shortname or url]
        • Gets all of the of the information from a remote repository and puts it in its "remote tracking" branches called [remote-name]/[branch-name]
        • Does not merge it or modify any of your work--this has to be done manually
        • --all gets updates from all remotes
        • Ex: fetch origin fetches any new work that has been pushed to that server since you cloned or last fetched from it
      • Merge into current branch using merge [remote-name]/[branch-name]
      • Peek at remote branches (without working on them)
        • Check them out directly: git checkout origin/dev
      • Create new local branches from remote tracking branches
        • checkout --track [new-local-branch-name] [remote-name]/[branch-name] (you can leave out local branch name if you want to use the same branch name as the remote)
    • pull
      • Generally fetches data from the server you originally cloned from and tries to merge it into the code you're currently working on
      • In general, you don't want to do this (use fetch and merge instead)
      • --all will download all remote branches
      • Pull and overwrite local, untracked files
        • git fetch --all to fetch the remote changes
        • git reset --hard origin/master reset local files to status of remote
    • push [remote-name] [branch-name] - send changes to remote repository
      • push origin master pushes your local master branch to the original server you cloned it from
      • Only works if you have write access to the server, and it hasn't been modified since you cloned/pulled it last
      • You have to pull the changes to incorporate them before you push, if you don't have the most recent version of the repository
      • push [remote-name] [branch-name]:[remote-branch-name] will specified a different name for the remote branch
      • git push [remotename] :[branch] will delete a remote branch from the server
      • -u will set as the default remote tracking branch (upstream) repository
      • -f will force updates (like when removing commits that have already been pushed)
    • PUSH directly to a website
      • Create a "bare" repository on the server, in a different directory than the web root: git init --bare
      • Create a file called post-update in the hooks folder of this new repository with the following line: GIT_WORK_TREE=/path/public_html git checkout -f (this should be the entire path to the web root, like public_html)
      • Give yourself execute permissions on this file: chmod +x /path/website.git/hooks/post-update
      • On the local machine, add a new remote server: git remote add [shortname] [user@server.com:/path/server/website.git]
      • Use this as the initial push command: git push website +master:refs/heads/master
      • Then you can use the normal push command to update the site: push [server-name] [branch-name]
  • Github

    • When forking a repository, create a new remote called upstream that links to the original repository so you can update it.
  • Managing Commits

    • commit - creates a current snapshot of the repository as it is
      • Opens a text file with a summary of the commit
      • -m is for including a message (in quotes) after the command that identifies the commit
      • -v will push the output of diff to the text file
      • -a will skip the staging area and automatically stage every file that is already tracked before executing the commit
      • --amend will add changes to previous commit
        • If you don't specify a new commit message with -m, you'll be given the previous commit message
        • Will create a conflict with commits that have already been pushed to a remote host
    • log
      • Default command lists the commits made in that repository in reverse chronological order
      • Note the difference between "author" (the person who wrote the patch) and "committer" (the person who executed the commit command)
      • -p shows the diff introduced in each commit
      • --stat shows abbreviated statistics for each commit
      • --shortstat displays only the changed/insertions/deletions line from --stat
      • --pretty=[format] changes the format of the log output
        • oneline prints each commit on 1 line
        • short, full, and fuller provide increasing levels of information
        • format: lets you specify your own formatting
      • --graph shows an ASCII graph with your branch and merge history
      • --name-only shows the list of files modified fate the commit information
      • --name-status shows the list of files affected with added/modified/deleted information as well
      • --abbrev-commit shows only the first few characters of the SHA-1 checksum
      • --relative date - display the date in a relative format
      • Limiting log output
        • -2 limits the output to the last two entries
        • --since= and --after=  get the commit history within a specified timeline, like --since=2.weeks or --until="2012-08-15"
        • --until= and --before= do the same but opposite of the above
        • --author= specifies a specific author
        • --committer= specifies the person who executed the commit
        • --grep= lets you search for keywords
        • use --allmatch if you use both --author and --grep commands for your query; otherwise all items that match each of your queries will be retrieved
        • Specify a directory of filename by adding it last, following --
      • gitk is a gui tool to visualize log searches (type gitk to activate it
    • Undoing changes - be careful, you can't always undo (revert) your undos!
      • commit --amend will add whatever is in your staging area to your most recent commit (uses the same commit message as the original, unless you change it)
    • reset [options] [hash/tag] [path/files] reverts to a previous commit
      • Points the head to the previous commit
      • Options
        • --soft keeps the state of the files the same, so you can see the differences (like undoing commits without changing the files)
        • --mixed also updates the index from the specified commit, as if your files were not yet staged for the next commit (this is used if nothing is specified)
        • --hard resets all files to the status of the preivous commit
      • Specify a path to effect only specific files or directories
      • Squash commits
        • Reset to the first commit you want to keep: reset --soft HEAD~2 (moves back 2 commits) or specify a hash
        • Run git commit
      • reset [hash/tag] file will reset the index (staging area) for that file, but not change the version in the working directory (until you commit, of course!)
    • revert [hash] - reverts to a previous commit
      • Will bring up an editor to revise your commit message (quit out of it if you don't want to change this message)
    • blame [path/filename]
      • Tells you who is responsible for a certain section of code
      • -L[start],[end] will only output specific lines
  • Tagging Files

    • Info
      • Often used for marking release points
      • Two kinds
        • Lightweight - a simple pointer to a specific commit
        • Annotated - full objects that have their own checksum, tagger name, tagger email, date, message, and can be signed and verified
      • Are not pushed by default 
    • tag lists the current tags in alphabetical order
      • -l '[search]' lists tags that have a particular format; Ex tag -l 'v1.4.*' will list all of the tags that begin with "1.4"
    • tag [name]-lw creates a lightweight tag
    • tag -a [name] creates an annotated tag
      • -m '[message]' creates a message; otherwise git opens the editor once a tag is created
    • show [tagname] will show information about a tag
    • Signed tags
      • tag -s [name] creates a new annotated tag signed with GPG (assuming you have a private key)
      • tag -v [tag-name] uses GPG to verify the tag signature (you need the signers public key in your keyring
    • Create a tag for a previous commit
      • Add part (or all) of the checksum at the end
      • Ex: tag -a v1.2 8fceb02
    • To push tags (just like sharing remote branches)
      • push [remote-name] [tag-name] would push the specified tag to the named remote server; Ex: git push origin v1.4
      • Use --tags option on the push command to push all of your tags; Ex: git push origin --tags
  • Branches

    • HEAD is what keeps track of which branch you're on; the default branch is "master"
    • branch
      • Lists the current branches, with a * to indicate the current branch you're on
      • -v lists the most recent commit with each branch
      • --merged shows which branches are already merged into the branch you're on (these you can usually delete because you've already merged the code)
      • --no-merged shows what branches that contain work you haven't merged
    • branch [branch-name]
      • Creates a new branch
      • -d deletes a branch
      • -D deletes a branch that hasn't yet been merged
      • -u [remote-branch] [local-branch] will set the specified local branch to track the specified remote branch (if no local branch is specified, the current branch is used)
    • checkout [branch-name]
      • Moves the head to another branch
      • If your working directory or staging area has uncommitted changes that conflict with the branch you're checking out, you can't switch branches
      • -b creates a new branch and switches to it
      • -a will list all branches, including those from remote repositories
      • Tracking branch
        • Use -b to create a local working copy of a remote branch you have fetched: git checkout -b [new-local-branch-name] [remote-name]/[branch-name]
        • --track is shorthand for this; Ex: git checkout --track [remote-name]/[branch-name] will create a new local branch with the same name as the remote branch
        • Tracking branches can then be pushed and pulled (when checked out) without specifying server information, because git remembers where they originally came from
        • Delete a remote branch from the server: git push [remotename] :[branch]
    • merge [branch-name]
      • Merges a branch into the branch you specify
      • If the working branch is just an addition commit of the branch merging-to branch (that hasn't been changed since the working branch was created), it just moves the pointer of the merged-to branch
        • Use merge --no-ff [branch-name] to prevent a fast-forward merge, if you want to maintain the history of the branch
        • --ff-only will abort the merge if not able to do a clean fast foward merge
      • If the merged-to branch has been changed, merges by recursive, creating a special "merge" commit that has more than one parent
      • You can merge the master into your working branch, if you need to (from the working branch): git merge master
    • Conflicts
      • Merge is paused if there are conflicts
      • See unmerged files in the status
      • To manually resolve a conflict
        • Remove everything between and including <<<<<<< and >>>>>>> and replace it with new code
        • add to stage the file (which marks it resolved)
        • Check that all conflicts are resolved and added with status
        • commit and specify what you did in the commit message
      • mergetool starts a visual merge tool to walk through conflicts
      • clean will recursively remove files that are not under version control
        • -f to force clean (required if clean.requireForce is not set to false)
        • -x will also remove ignored files (specified under .gitignore; normally these files are not cleaned)
        • -d remove untracked directories in addition to files
        • -n or --dry-run
        • -X remove only files ignored by git
    • Remote Branches
      • References to the state of branches on your remote repositories
      • Take the form [remote]/[branch]
      • Never updated automatically--you must push the branches you want to share (this way, you can keep your code private until it's ready to share
    • Rebasing
      • Take all changes that were committed on one branch and replay them on another branch
        • Essentially takes the branch and makes it the next commit in the branch you're rebasing to
        • You can then do a fast-forward merge, because both commits are in the same stream
        • Has the advantage that it leaves a clean history; good for contributing to a project that you don't maintain, because it will just add another commit to the main branch
      • rebase [branch-to-rebase] (from within the target branch to which you are rebasing)
      • rebase [branch-to-rebase] [target-branch] will take the branch to rebase and rebase it into the target branch without checking out the target branch (you are then switched to the target branch)
      • --onto rebases a branch over top of another one, if the working branch and rebase-to branch aren't directly connected
      • DO NOT rebase commits that you have pushed to a public repository--instead use it as a way to clean up work with commits before you push them
      • Interactive rebase - squashing commits
        • -i HEAD~n where n is the number of commits to squash
        • You can then edit the rebase process to combine or skip commits as needed
        • Use mergetool to fix any merge conflicts while rebasing
        • --continue will continue the rebase once merge conflicts are fixed
        • --skip will skip the current commit that has an error
        • --abort will abort the rebase and return to the most recent commit
    • cherry-pick [commit] - add a specific commit
      • Ex: cherry-pick master [commit-hash] would add that commit from master to the current branch
  • Subtree

    • subtree split breaks off a subfolder, extracts the relevent commits, and allows it to become it's own repo
      • Ex: git subtree split --prefix=web --onto deploy -b deploy takes the web folder and puts it in its own branch called deploy
        • --prefix= selects the folder
        • --onto selects an existing subtree to apply the split (you don't need this the first time you run this command)
        • -b declares which branch to use, and creates it if it doesn't exist
  • Submodules

    • Usage
      • Used for putting additional repositories within an existing repository
      • Submodules should be self-sufficient repositories of code
      • Good way to include dependencies for your project
      • IMPORTANT: submodules are tracked separately; if you change files in a submodule, they are not tracked by the parent project! You must commit changes them with each submodule independently
      • The "superproject" only keeps track of the status of a submodule by its current commit, when a commit is made to the superproject
      • git push does NOT push a submodules code; push the changes from the submodule first
    • Best practices for editing submodules
      • Don't edit submodules themselves; instead edit them as separate projects and pull them into the superproject
      • If you do edit submodules directly
        • They will be overwritten with submodule update
        • Create a new branch within them first, git checkout -b [branch-name] so you can find your work in that branch later, if you need to
    • Adding submodules
      • submodule add [source] [path/name]
      • The submodule will be cloned in the specified directory (sub submodules of this submodule will be empty directories)
      • submodule init adds .gitmodule info to .git/config file
    • Managing submodules
      • submodule status
      • submodule summary compares submodules current state vs commited state
      • Merging branches
        • Whenever you merge in a branch, submodule updates are not included
        • Submodule updates must be done separately (see below)
    • Updating submodules
      • submodule update
      • Updated submodules are "headless", meaning they don't have a current branch
        • Switch to submodule directory
        • git checkout master (or create a new branch)
      • pull (from submodule directory)
      • Make a commit in the superproject to save the state of all submodules. If submodules updates are not committed, they are lost
    • Removing submodules
      • Remove submodule reference from .gitmodules
      • Remove submodule reference from .git/config
      • Remove the created path for the submodule: git rm --cached [path] (no trailer slash)
    • Cloning with submodules
      • clone (submodule directories will initially be empty)
      • submodule init to initialize the submodules
      • submodule update to pull in the contents from the submodules
      • Shortcut
        • clone [source] --recursive to include submodules (and their submodules) in a clone
        • submodule update --init --recursive if you forgot to do this on the initial clone
    • Branches and submodules
      • When switching to a new branch without a submodule, the submodule directory remains, untracked
        • If you move or remove it, you'll have to clone it back when you switch back to the original branch
    • Convert subdirectory to submodule
      • Move the subdirectory out of the superproject into its own project space
        • Use git filter-branch to extract repository history just for this folder, if needed
      • Rename the submodule-to-be directory if needed, to be more descriptive
      • Create a new upstream hosting for the submodule (like GitHub)
      • Initial new project git init and push to remote, if needed
      • Add new git module to superproject
      • Commit and push the super project
      • If you switch back to a branch that doesn't have the submodule but still has the directory, you need to move the directory out of the way first, so it doesn't get overwritten by the subdirectory from the branch
  • Debugging

    • bisect to find the exact commit where a bug appeared
      • bisect start to get going
      • bisect bad to mark the current commit that has the bug
      • bisect good [commit] to name a past commit where you know things worked
      • The bisect session will start
      • Test the bug at each stopping point and enter bisect good or bisect bad accordingly
      • Git will tell you the first commit where the bug appeared
      • bisect reset to return to where you started
  • Configuring a git server

    • Server setup
      • Install git on the server
      • Create a git user
      • Add a ssh key for each person who will be able to access the git user
      • Change the shell for the git user in /etc/passwd to git-shell (run which git-shell to determine where it's located)
      • Create a directory in the git user's home directory called git-shell-commands
    • Gitolite
      • Install

        • Create a git user (as above, but maintain regular shell access and don't add keys) and login

        • ~/.ssh/authorized_keys needs to be empty or non-existent

        • Upload your ssh public key to $HOME/YourName.pub

        • Run the following commands

            git clone git://github.com/sitaramc/gitolite
            mkdir -p $HOME/bin
            gitolite/install -to $HOME/bin
            source .profile
            gitolite setup -pk YourName.pub
          
      • Add users

        • Do this using the special repository, gitolite-admin

        • git clone git@host:gitolite-admin on your local machine

        • Keys - add users' public keyfiles ([user].pub) in the keydir

        • Repos

          • Edit the file conf/gitolite.conf

              repo foo
              	RW+                     =   alice
              	-   master              =   bob
              	-   refs/tags/v[0-9]    =   bob
              	RW                      =   bob
              	RW  refs/tags/v[0-9]    =   carol
              	R                       =   dave
            
          • Commit the changes and push to your git server

        • Permissions

          • R read
          • W write
          • + allows deleting refs
          • - to deny access
        • Groups

            @staff      =   alice bob carol
            @interns    =   ashok
          
            repo secret
            		RW      =   @staff
          
            repo foss
            		RW+     =   @staff
            		RW      =   @interns
          
        • @all

          • Can be used as an alias for all users or as an alias for all repos

              repo @all
              	R = @all
            
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment