-
Notifications
You must be signed in to change notification settings - Fork 435
A Git Tutorial
Pragmatic Version Control Using Git, Travis Swicegood (The Pragmatic Bookshelf, Raleigh, 2010) Another excellent online source can be found here.
Not so relevant for Psi4 development, but FYI git can also be very useful for your own private projects. To make a repository for your own (non-shared) use
cd [project-directory-name]
git init
This will make a local repository and store it in a .git subdirectory. That's all there is to it! Migrating your repository to another computer is as simple as copying the working directory (and its included .git subdirectory) where you want it to go.
To clone a local repository
git clone other-repository-location proj-dir
This will make a clone of the remote repository and put it in a local directory called proj-dir.
To clone a remote repository
git clone username@machine://remote-repository proj-dir
where username@machine specifies an SSH login to a remote machine.
Alternatively, if you have access to a repository that's accessible via an https interface, you can use
git clone https://remote-repository proj-dir
you may be prompted for a username and password for the site.
For Psi4, if you are registered with github, you can use your github account information to clone via https. Alternatively, you can also use SSH if your SSH key is registered with the project on github. Most commonly, to clone the public repository,
git clone https://github.com/psi4/psi4public.git [local-dir-name]
and to clone the private repository (if you have access),
git clone https://github.com/psi4/psi4.git [local-dir-name]
Sometimes you'll have files, like vi swap files with names like .file.swp, that you don't want git to worry about. This is easy to fix, just add the relevant filenames or wildcard filenames (like *.swp) to the file .gitignore. Alternatively, if this is a personal preference appropriate to a particular developer, and maybe not appropriate to adding to a shared repository, exclusions like this can be added to the local .git/info/exclude file. Psi4 already contains a .gitignore file with some common suffixes of files that should not be checked into the Psi4 repositories.
To add a new file or files to the repository,
git add [filename-list]
and then check it in with
git commit -m "log message"
git rm filename
Follow this up with git commit
git mv oldname newname
Note: we had trouble once trying to move a file in a different directory. Make sure you're moving a file in the same directory you're in.
Follow this up with a git commit
Checking in changes to a local repository requires two steps: (1) staging the change, and (2) committing the change.
git add [filenames]
git commit -m "log message"
Note: if you have a really long log message, you can split it up and specify multiple -m "" arguments.
a shortcut to the above procedure is
git commit -m "log message" filenames
To automatically stage all changed files and commit the changes,
git commit -a -m "log message"
Note: git add filename will stage a commit of the changes to the given file at the time of the git add command. If you change the file again, you need to do git add again to stage the latest change.
If you have a large number of changed files, and you want to review them one by one, you can enter the "interactive" mode of git add by typing
git add -i
If you made many changes to a file, you might need more information than just the list of changed files. You can look at diffs with the diff command within git add -i, or via git diff. Alternatively, you can review the individual changes at staging time, and even selectively apply some and leave others unstaged, by using ::
git add -p
which enters "patch" mode (one change at a time). (You can also look at patches from within git add -i using the "patch" option in the interactive menu.)
To unstage a staged commit
git reset HEAD file-to-unstage
To merge in changes from a remote repository:
git pull [remote-repository] [remote-branch-to-pull]
where remote-branch-to-pull does not include the origin/ prefix. This fetches and merges changes from the remote repository into the local repository. The merge may lead to conflicts. If that happens, see below for resolving conflicts.
To bring over changes from a remote repository but not merge them yet:
git fetch
This updates remote branches but does not merge changes into your local branch yet until you do a subsequent git commit. Not as commonly used as git pull.
To update the remote repository with (committed) changes from your local repository:
git push [--dry-run] [remote-repository] [refspec]
pushes committed changes from the local repository to the remote repository. The --dry-run option
shows you what changes would be pushed. refspec is a tag, branch, or keyword like HEAD; e.g., mybranch:master pushes changes from mybranch to the remote master branch. The default remote repository is named origin.
Sometimes you might get a conflict when trying to do a merge. In such a case, the file(s) with the conflict will have the standard "conflict markers." The beginning of the conflict region will be marked with "<<<<" symbols and the name of the current branch and file. That version of the file will continue until another marker of "======" symbols. Then, the conflicting version of the text will appear until closed off by ">>>>" symbols and the name of the conflicting branch and filename. Multiple areas of conflicting text might appear in a single file.
Edit the file by hand to resolve the conflict. Make sure you delete the conflict marker lines beginning with ">>>", "<<<", or "===". We don't want those checked in. Once the file is the way it's supposed to be, git add the conflicted file and do a git commit. In this particular case, if you leave off a log message, git will automatically create one specifying that it's related to a merge.
If the conflict isn't trivial to resolve, it can be useful to use git mergetool, which brings up a native text editor to show the conflict details clearly.
Occasionally, you will just want to accept the remote version of the file, or just keep your version of the file (although make sure you aren't losing any important changes by deciding to do this!). In such a case, to keep the remote version (and if you have git version 1.6.1 or newer), use git checkout --theirs [filename], and to keep your version, use git checkout --ours [filename]. This would be followed by git add [filename] and git commit.
To undo the latest commit, do
git revert
To undo multiple previous commits, stage them before committing with the -n flag
git revert -n [commit-id, or HEAD for most recent]
git revert -n [other-commit-id]
...
Revert the most recent commit first, then the earlier commits, in order. Finally,
git commit -m "revert [commit-id1] and [commit-id2]"
If you don't give -m, git will make an automatic revert log message for you
To check on the status of the working copy
git status
This will list files that are modified, staged for checkin, etc.
To see the differences between changes that are staged and what's in the repository,
git diff --cached
To see differences between the working directory AND what's staged vs the repository,
git diff HEAD
To see differences between the working tree and a previous revision,
git diff [revision-id]
To get a summary of how often files have changed since a revision or tag,
git diff --stat [tagname or revision-id]
To view the diffs that a revision created, use
git log -p
To print the git log
git log [-n] [--pretty=oneline]
will print the last n log entries.
To view the log starting from a given revision, give the revision number (just enough characters, like 7, to be unique) as an argument
git log [revision-id]
To view recent changes happening in the last certain timeframe,
git log --since="7 hours"
where instead of "7 hours" you can put "1 day" or "2009-12-01", etc.
To get a list of changes from a particular commit or tag until now, and in a short summary format,
git log --pretty-format:"%h %s" 1.0..HEAD
You can also use the character to mean the revision before a given revision. 6f1bf6f is the revision two revisions before 6f1bf6f.
To see who changed a particular file, so you know who to blame for an error
git blame filename
To see who changed line 208 or the following 5 lines in filename,
git blame -L 208,+5 filename
You can also search text instead of linenumbers using regular expressions
git blame -L "regexp",+5 filename
Branches can be created to allow you to continue development in a way that your changes are kept separate from the main trunk. Then, when you're done, you can merge changes from your branch back into the main trunk so that other developers will have them. For work that is meant to be kept private before release, use a branch on the private Psi4 repository. Otherwise, you can create a branch within the public repository. (Note: synchronization between private and public Psi4 repositories is only regularly performed for the main trunk, not branches)
git branch new-branch-name branch-to-create-from
where branch-to-create-from is a branch name or tag (often branch "master"). If the final parameter is not present, git will assume you want to create a branch off of the current working branch. Note that creating a new branch does NOT automatically switch the working copy to that branch. Use git checkout to do that. A shortcut to avoid having to do that is ::
git checkout -b new-branch-name branch-to-create-from
This will create a new branch and check it out in one step.
To switch the working copy to a different branch ::
git checkout branch-name
Warning: you can lose unsaved changes when doing this
To see a list of all available local branches ::
git branch
The name of the current working branch will be marked by a *
To see a list of all available remote branches ::
git branch -r
This lists branches in the remote repository. These branches can be checked out, but they should not be changed. If you want to change them, create a local branch from them first, and then make the change. The remote branches will be named with an origin/ prefix to keep them distinct from local branches.
To rename a branch
git branch -m oldname newname
First, figure out what branch you want to absorb changes from another branch (usually, master). Assuming the other branch is already up-to-date and checked in, switch to the branch you want to absorb the changes::
git checkout master
Now, apply changes from the other branch on top of the current one::
git rebase other-branch-name
That should do it.
If you're done with the other branch once it's absorbed back into the master branch, you can delete it with ::
git branch -d other-branch-name
If you had to experiment quite a bit in an alternate branch before getting something right, you might not want to commit all the individual changes, but just the final changes in the alternate branch relative to the main branch. In a case like this, finish checking in any changes on the alternate branch, check out the master branch ::
git checkout master
merge all the changes from the alternate branch in squashed form, ::
git merge --squash alternate-branch
note that squashed merges DO NOT update the master branch repository yet. They must be committed with ::
git commit -m "log message"
If you want to merge in just one change from another branch, you can use the fact that commit identifiers are unique across the entire repository to select the commit you want to merge. Get the commit ID by noting it after it's printed in git commit, or from the log file (you don't need the entire ID number from the logfile, the first 7 characters or so should do it). Then, switch to the branch you want to merge changes into, like
git checkout master
Finally,
git cherry-pick [commit-id]
To cherry-pick multiple commits, do ::
git cherry-pick -n [commit-id-1]
git cherry-pick -n [commit-id-2]
...
This will do the merges but will not automatically do a commit.
You can tag a specific point in the repository by
git tag tag-name branch-name
You can view a list of all the tags in the repository by running
git tag
with no arguments
To create a gzip archive of the project as of some specific tag
git archive --format=tar
--prefix=proj-dir tagname
| gzip > proj-dir.tar.gz
where proj-dir is the name of the directory for the working copy, and tagname is the name of a tag for the version you want to archive.