Navigation:

The GIT repo

Adding your computer to push/pull/clone from !GitLab

You will have access to GitLab from the web interface, but if you want to use the tools described in this tutorial you will need to allow your computer to push/pull to/from the repository at GitLab. This is done via SSH-keys.

Now you will be able to clone/push/pull etc from the server.

Getting started

First get an account on the repository. Ask me for that. Once you can log in via the web interface, proceed to the next step.

Important

See the tutorial at Gitlab.com.

Important

See this page on using Git sensibly: A Successful Git Branching Model. Here is a PDF version of that page.

Setting up GIT

SSH Keys

The Git repo can be used from the web interface without any special settings, but to use it from the command line you will first need to insert your SSH public key into the repo. To generate your key use

  $ ssh-keygen -t rsa -C "USER@MACHINE"

where USER is your user name and MACHINE is the machine from which you will use Git. The key will be in file (by default):

  $ cat ~/.ssh/id_rsa.pub

To insert the key into GitLab you need to get to Profile Settings and then find SSH Keys (short form: Profile Settings/SSH Keys). Paste it there. There are more detailed instructions available through a link on that page.

Git global setup

There are some settings you will want to include in the ${HOME}/.gitconfig file. Here are a few. Change the field values to suite your account.

  git config --global user.name "Dr Alston Misquitta"
  git config --global user.email "a.j.misquitta@qmul.ac.uk"
  git config --global diff.tool vimdiff
  git config --global merge.tool vimdiff
  git config --global difftool.prompt false
  git config --global core.excludesfile ~/.gitignore_global

The user.name and user.email settings are basic settings for your account. However I'm not sure where they are needed.

Here's an explanation for the rest:

Clone the master copy

git clone is the analogue of svn checkout. But, as far as I can tell, Git will by default obtain the master copy of the project. If you want a branch, you need to do an additional step. Here is how we clone the CamCASP project.

  $ git clone git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP.git
  $ ls
  $ CamCASP
  $ cd CamCASP
  ChangeLog          KnownBugs.list     README.md          copy-binaries.bash examples           osx
  ChangeLog-5.9      Makefile           To_do.list         data               g95                patch
  Changelog-5.6.07   Makefile_body      VERSION            design             interfaces         src
  Changelog-5.7      Makefile_body_test basis              dev                log                utilities
  Changelog_2005-09  Makefile_c         bin                distrib            makeall            x86-32
  INSTALL            README             bug-reports        docs               misc               x86-64

  $ git branch
  * master

At this stage you have a working copy of the master.

=== Clone a branch ===

  git clone -b <branch> <remote_repo>

Use the -b option to clone a branch instead of the master.

Update branch list from remote

If a new branch has been pushed to origin by someone else you will not be able to see it using //git branch -r// till you update the branch list using

  git remote update origin --prune 

Create a branch on remote

To create a branch on local you use

  git checkout -b <name of branch>

This branch can then be pushed to the remote repo using

  git push origin <name of branch>

To create a remote branch directly use

  git checkout --track origin/<branch name>

This will create a remote and local branch. Both with the same name.

This is equivalent to using

  git checkout -b <branch name> origin/<branch name>

but this command can be used with different names for the local and remote branch:

  git checkout -b branchA origin/branchB

When the branch is tracked both remotely and locally, commands like git pull/push should work without specifying the name of the remote branch. (needs to be verified)

Delete a branch locally and on remote

To delete branch locally

  git branch -d localBranchName

And to delete branch remotely

  git push origin --delete remoteBranchName

Switch to a development branch

If you are working on a project with someone and have permissions to view a development branch, then you can obtain one using the following commands:

  $ cd CamCASP
  $ git branch
  * master  <--- you see only the master. No branches!
  $ git checkout -b dev-ajm-6.0  origin/dev-ajm-6.0
  Branch dev-ajm-6.0 set up to track remote branch dev-ajm-6.0 from origin.
  Switched to a new branch 'dev-ajm-6.0'
  $ git branch
  * dev-ajm-6.0   <--- the '*' indicates the branch you are on.
    master

To switch back to the master use

  $ git checkout master
  Switched to branch 'master'
  Your branch is up-to-date with 'origin/master'
  $ git branch
    dev-ajm-6.0
  * master        <--- we are now in the master

Where is the repo located (remote url)?

Use this command to find out the location of the remote repo:

Status of the repo

Find out the status using

But if you have a lot of file present that are not in the repo you will get a lot of unwanted information. To limit the information in the output to the files that are part of the repo use

or, if that is too long, use

Status against a remote repo

If you are using a remote repository and want to know the status of your repo against that one you need to do the following:

Here is more information from http://stackoverflow.com/questions/3258243/check-if-pull-needed-in-git:

However, it looks like you want to do this in a script or program and end up with a true/false value. If so, there are ways to check the relationship between your current HEAD commit and the head of the branch you are tracking, although since there are four possible outcomes you can't reduce it to a yes/no answer. However, if you're prepared to do a pull --rebase then you can treat "local is behind" and "local has diverged" as "need to pull", and the other two as "don't need to pull".

You can get the commit id of any ref using git rev-parse <ref>, so you can do this for master and origin/master and compare them. If they are equal, the branches are the same. If they're unequal, you want to know which is ahead of the other. Using git merge-base master origin/master will tell you the common ancestor of both branches, and if they haven't diverged this will be the same as one or the other. If you get three different ids, the branches have diverged.

To do this properly, eg in a script, you need to be able to refer to the current branch, and the remote branch it's tracking. The bash prompt-setting function in /etc/bash_completion.d has some useful code for getting branch names. However, you probably don't actually need to get the names. Git has some neat shorthands for referring to branches and commits (as documented in git rev-parse --help). In particular, you can use @ for the current branch (assuming you're not in a detached-head state) and @{u} for its upstream branch (eg origin/master). So git merge-base @ @{u} will return the (hash of) the commit at which the current branch and its upstream diverge and git rev-parse @ and git rev-parse @{u} will give you the hashes of the two tips. This can be summarized in the following script:

git_repo_status.bash

<code | git_repo_status.bash>
#!/bin/sh

UPSTREAM=${1:-'@{u}'}
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")

if [ $LOCAL = $REMOTE ]; then
    echo "Up-to-date"
elif [ $LOCAL = $BASE ]; then
    echo "Need to pull"
elif [ $REMOTE = $BASE ]; then
    echo "Need to push"
else
    echo "Diverged"
fi

Note: older versions of git didn't allow @ on its own, so you may have to use @{0} instead.

The line UPSTREAM=${1:-'@{u}'} allows you optionally to pass an upstream branch explicitly, in case you want to check against a different remote branch than the one configured for the current branch. This would typically be of the form remotename/branchname. If no parameter is given, the value defaults to @{u}.

The script assumes that you've done a git fetch or git remote update first, to bring the tracking branches up to date. I didn't build this into the script because it's more flexible to be able to do the fetching and the comparing as separate operations, for example if you want to compare without fetching because you already fetched recently.

Ignore files in a repo: .gitignore

You can also create a global .gitignore file, which is a list of rules for ignoring files in every Git repositories on your computer. For example, you might create the file at ~/.gitignore_global and add some rules to it.

Explicit repository excludes

If you don't want to create a .gitignore file to share with others, you can create rules that are not committed with the repository. You can use this technique for locally-generated files that you don't expect other users to generate, such as files created by your editor.

Use your favourite text editor to open the file called .git/info/exclude within the root of your Git repository. Any rule you add here will not be checked in, and will only ignore files for your local repository.

Add files (staging) to the repo

This is done using

All that this does is to add the files to your local copy.

Stage and Commit changes

Important

For more on recording changes to the repo see Recording changes to the repo.

Staging is the Git way of adding changes to Git. You edit/create a new file. Then use git add as above to stage it to the repo. Now Git knows about the change. If you modify the file/s subsequently, Git will still use the versions at the staging step!

Now you will need to commit the changes. This is done using:

This will commit all staged changes to the repo. But none of the changes will be in the remote repository; they are so far all local. To move the changes to the remote repo use:

where REMOTENAME is usually origin and BRANCHNAME will be the name of the branch to commit to. So to commit to the master use

and to commit to a branch dev-ajm-6.0 use

Update a branch

To update from a remote repo use

Revert changes

If you have made changes to a file but want to un-do them by reverting to the original version use:

The -- is needed to account for the case when you have a file and branch with the same name.

If you want to throw out all of the changes you’ve been working on, there’s two ways to do that.

Once these commands are run you will lose all of the work that isn’t committed in your directory, so make sure to take caution when using them.

Differences

Under Subversion we use svn diff to obtain differences against the repo, or a particular version in the repo, or just against the local copy. The output of svn diff is not too useful if we have a lot of changes, so we use things like ''svndiff2'' to make a tidier output using vimdiff. The same works for Git, only it is much easier and you don't have to install anything. Here's some information I obtained from Git and Vimdiff:

To show the changes that are not currently staged for the next commit:

If you've already staged changes,

will show the difference between the index and the last commit. Both commands can result in a lot of output. To limit the information to the file names only use

To show a diff between the current working directory and the named branch:

This will show all the differences, which may be overkill. To show only the names of the files that have changed between the working directory and the named branch use

To see an overview of changes use:

A related command is

which shows commit history with the files that were changed.

Git Settings

Fortunately for us Vim fanatics, it's fairly easy to get Vimdiff working with Git. Git has a lot of options for working with diffs, and one is diff.tool (man page: man git-config). Also related is the merge.tool setting which can be set to allow Vimdiff to be used as the merge resolution program.

These settings can be passed to Git without changing any configuration files:

To tell Git to always use Vimdiff, issue the following commands:

Omit --global if you just want to set these for the repository in the working directory. Now typing git difftool should bring up Vimdiff.

Another useful option is difftool.prompt -- this will stop Vim prompting about launching vimdiff:

If you're just trying these commands out, then the prompt will seem annoying, but there are cases where you might want to ignore a few files so it can be useful.

Once you've put these settings in your .gitconfig file then to see differences in files use

This will launch vimdiff for the diff. To see the diff directly on the screen, as always, use

Editing Changed Files

To edit all of the changed files in tabs in vim use the following:

It works, but I'm not sure how useful this is as the differences are not shown.

Renaming files

Differences between versions of files

To take the difference between two commits of the same file use:

The commit tags are cumbersome to use, and in any case, we usually need differences w.r.t. the present version and others before or after it. There are a few shortcuts to use:

So to compare the present version of stockholder.F90 with one committed three versions back use:

  git diff HEAD~3 stockholder.F90

To use vimdiff use:

  git difftools HEAD~3 stockholder.F90

The following are also supposed to work:

  git diff HEAD^^ HEAD <file>
  git diff HEAD^^..HEAD -- <file>
  git diff HEAD~2 HEAD -- <file>

I had some trouble with these.

Differences between files on different branches

If the files are in different paths on the branches, use:

  $ git difftool branch1:old/path/to/file branch2:new/path/to/file

More modern syntax:

  $ git diff ..master path/to/file

The double-dot prefix means "from the current working directory to". You can also say:

Keeping a branch up-to-date with the master

This is quite important as you do not want a branch to get too far out-of-date with the master. Here's how you can keep your branch and master up-to-date.

What you need to do is to periodically (often enough!) update your local master and then merge those changes into your local branch. You may then, if needed, push your local branch to the origin/branch (i.e., the one on the repository).

First switch from your local/branch to your local/master:

  git checkout master

Now make this local/master up-to-date with the master on the repository at origin/master:

  git pull

Some people say we should do a rebase using:

  git pull --rebase

Now go back to your local/branch

  git checkout branch

And merge the newly updated local/master with the local/branch

  git merge master

At this stage you may get conflicts. Resolve then and then do a commit to the local/branch

  git commit

Now your local/branch is up-to-date with local/master which is up-to-date with origin/master. If you do a push now to origin/branch:

  git push origin branch

then origin/branch will not only contain your most recent changes, but will be up-to-date with origin/master.

Merging a branch into the master

I had a lot of trouble with this one till Mary provided a solution. Here's the scenario:

here's what you do:

  git branch
  * b1
    master

You are on local branch local/b1. Checkout the local master and merge your changes in branch local/b1 into the local/master.

  git checkout master
  git merge b1

Now you have merged changes from branch local/b1 into local/master. At this stage you may get conflicts (I did). Resolve them. Then add and commit them:

  git add file1 file2
  git commit

This makes local/master conflict free. Now your changes in branch local/b1 are in local/master. Before doing a push to the repo version origin/master, check to see if any changes have been made there:

  git pull origin master

Resolve any possible merge conflicts, and commit the merge if prompted. If all is well (test the code - I forgot to do this!), push the changes to the repo:

  git push origin master

After you've done this, both local/master and origin/master should be up to date with respect to one another. Now you can go back to your local branch (either master or dev) and make new changes.

And now to switch back to your branch use:

  git checkout b1
  git rebase master

I am still unsure why we need the last 'rebase' step. Here is Mary's explanation:

// To answer your second question, rebase is a command which is similar (in end result) to git pull, but which treats the version control and history slightly differently. You can check out this blog post if you're curious about the details. But in effect, performing 'git rebase master' on your dev branch will pull in any changes from local/master to your dev branch so that your dev branch can be kept up to date with respect to the master. //

At the end of this process local/b1 is up-to-date with local/master and origin/master. Now you can work on more changes to the branch!

Add or Modify location of remote repository

Important

If you change remote repositories or pull from one but need to push modifications to another then you will need to know how to add/modify remote repo locations.

To find out what your remote location is at present use git remote -v:

$ cd CamCASP
$ git remote -v
origin  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP.git (fetch)
origin  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP.git (push)

When you use git push origin <branch> this is where the push is made to.

We can add another remote, let's call it origin-new. This repo has already been created and is located at git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP_new.git. Here's how it can be added to the list of remote repos:

$ git remote add origin-new git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP_new.git
$ git remote -v
origin  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP.git (fetch)
origin  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP.git (push)
origin-new  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP_new.git (fetch)
origin-new  git@git.ph.qmul.ac.uk:a.j.misquitta/CamCASP_new.git (push)

Now pushes can use the new repo:

$ git push origin-new <branch>

Check status against remote repository

Important

Q: How do I check whether the remote repository has changed and I need to pull?

http://stackoverflow.com/questions/3258243/check-if-pull-needed-in-git

First use

  $ git remote update

to bring your remote refs up to date. Then you can do one of several things, such as:

If you use -v with git remote update (git remote -v update) you can see which branches got updated, so you don't really need any further commands.

However, it looks like you want to do this in a script or program and end up with a true/false value. If so, there are ways to check the relationship between your current HEAD commit and the head of the branch you are tracking, although since there are four possible outcomes you can't reduce it to a yes/no answer. However, if you're prepared to do a pull --rebase then you can treat "local is behind" and "local has diverged" as "need to pull", and the other two as "don't need to pull".

You can get the commit id of any ref using git rev-parse <ref>, so you can do this for master and origin/master and compare them. If they are equal, the branches are the same. If they're unequal, you want to know which is ahead of the other. Using git merge-base master origin/master will tell you the common ancestor of both branches, and if they haven't diverged this will be the same as one or the other. If you get three different ids, the branches have diverged.

To do this properly, eg in a script, you need to be able to refer to the current branch, and the remote branch it's tracking. The bash prompt-setting function in /etc/bash_completion.d has some useful code for getting branch names. However, you probably don't actually need to get the names. Git has some neat shorthands for referring to branches and commits (as documented in git rev-parse --help). In particular, you can use @ for the current branch (assuming you're not in a detached-head state) and @{u} for its upstream branch (eg origin/master). So git merge-base @ @{u} will return the (hash of) the commit at which the current branch and its upstream diverge and git rev-parse @ and git rev-parse @{u} will give you the hashes of the two tips. This can be summarized in the following script:

git-check-against-remote.bash

  <code | git-check-against-remote.bash>
#!/bin/sh

UPSTREAM=${1:-'@{u}'}
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")

if [ $LOCAL = $REMOTE ]; then
    echo "Up-to-date"
elif [ $LOCAL = $BASE ]; then
    echo "Need to pull"
elif [ $REMOTE = $BASE ]; then
    echo "Need to push"
else
    echo "Diverged"
fi

Note: older versions of git didn't allow @ on its own, so you may have to use @{0} instead.

The line UPSTREAM=${1:-'@{u}'} allows you optionally to pass an upstream branch explicitly, in case you want to check against a different remote branch than the one configured for the current branch. This would typically be of the form remotename/branchname. If no parameter is given, the value defaults to @{u}.

The script assumes that you've done a git fetch or git remote update first, to bring the tracking branches up to date. I didn't build this into the script because it's more flexible to be able to do the fetching and the comparing as separate operations, for example if you want to compare without fetching because you already fetched recently.

Command line instructions : In brief

Git global setup

  git config --global user.name "Dr Alston Misquitta"
  git config --global user.email "a.j.misquitta@qmul.ac.uk"

Create a new repository

  git clone git@git.ph.qmul.ac.uk:CCMMP/PotentialDev.git
  cd PotentialDev
  touch README.md
  git add README.md
  git commit -m "add README"
  git push -u origin master

Existing folder or Git repository

  cd existing_folder
  git init
  git remote add origin git@git.ph.qmul.ac.uk:CCMMP/PotentialDev.git
  git add .
  git commit
  git push -u origin master

AJMPublic/camcasp/git (last edited 2021-07-14 13:45:29 by apw185)