Git
This chapter describes using the command line tool git for version control when when interacting with the Drupal.org repo. It also introduces the Drupal.org project pages, and how to interact with the issue queue and source code repository accessible via those pages.
Table of contents
- Introduction
- Install git
- Version control with git
- Creating a project
- Basic local workflow
- Tags
- Interacting with remotes
- Collaboration
- Sandboxes and full projects
- Troubleshooting
- Final word
Introduction
The Drupal community makes extensive use of git, a distributed version control system. It is used to manage source code and to share code with one another.
Understanding how to use git is essential if you want to use hot-fixes available on the Drupal.org site before they are released, if you want to contribute patches to Drupal projects, and if you want to maintain a project yourself.
To work with git at Drupal.org, you need to create a user account by registering at the site, and obtain git access. To familiarise yourself with the way Drupal.org uses git see the Drupal.org git FAQ.
The Drupal.org issue queue
Each extension hosted on Drupal.org, whether a module or theme, has its own project page. For an introduction to project pages, please see the section in this ebook introducing extensions. The project page for jQuery Update is reproduced below:
In the right margin of project page, under the heading “All issues”, are links to the project's issue queue. The issue queue is simply a list of messages from maintainers and users related to that project.
The issue queue an essential tool in the Drupal community. It is what makes it possible for community members to cooperate without attending physical meetings, and without knowing each other personally.
If you need help with a specific project, whether a module or a theme, you should go to the issue queue where the maintainers and active users of the module or theme will be hanging out. This makes it more likely that your question will be seen by people who can help you. Additionally, you can use the issue queue to report bugs in the module or theme.
The issue queue is not a forum. The issue queue allows any project user to post an issue summary to a specific project version and categorise it (e.g., bug report, feature request) and suggest a priority. Subsequently, the project maintainer assigns tasks to colleagues, and tracks the status (e.g., active, fixed). In this way, all specific concerns related to a specific project can be tracked.
Note that the issue summary is a wiki-page. As work on the issue progresses the issue summary should be updated with the latest status information.
When commenting on an issue for a project on Drupal.org, the recommended practice for linking to the issue is to just list the issue number in square brackets like the example below:
<ul> <li>[#650306].</li> </ul>
This will automatically expand to the full issue's full title, with a link back to the issue.
Please also read the Drupal.org documentation: Use the issue queue and Using the issue queue.
Install git
First, check if git is already installed:
$ git -bash: git: command not found
If the command is not found, you first need to install git.
To install git on RHEL, do:
$ sudo yum install git
To install git on Ubuntu/Debian Gnu/Linux, do:
$ sudo apt-get install git
If you use some other platform, you'll find git installers and installation guidelines for most platforms on the website git-scm.com. If you're using a Gnu/Linux operating system with a package manager, you should be able to use that to install git.
Version control with git
A version control system keeps track of the history of a source code repository (main project directory) and lets the developer change the repository into another state. A distributed version control lets the developer work with a local copy of the a repository. With git, every local copy contains the full history and lets you do almost anything locally.
One of the main advantages of using a version control system is branching. You can work on a stable development branch named “1.0.x”. At the same time you, or someone else in the development team, can work on a specific bugfix on parallel in a separate branch named “bugfix”, and on a new feature in a third branch. Having the ability to branch means that your stable development branch is not disturbed by these parallel efforts until it is ready. Then the result can be merged back into the development branch.
In the illustration above, the green squares are snapshots of the source code of a particular project at certain points in time, and the grey squares are branch pointers. The red square is a special pointer named “HEAD”. The horizontal dimension in the illustration is time, flowing from left to right. There has been three branches of the source code, but the bugfix branch is already merged back into the 7.1-1.x development branch, while the feature branch is moving ahead on its own.
Don't worry if you do not understand every detail in the illustration or the terminology at the present point. This is just a broad overview. A more detailed explanation will follow, and when you reach the end of this chapter, you should find this diagram much clearer.
With git, you're allowed to clone repositories, e.g. create a local copy that is an exact copy of some existing repository including the complete history of the source code. Owners of repositories can synchronise changes to linked local copies of repositories via push (transferring changes to a remote repository) or pull (getting changes from a remote repository).
Git is a command line tool and is best used from the
command line in a Unix or Gnu/Linux environment. A git
command is the first word following “git” on the command
line. For instance, to use the git help
command,
you type:
$ git help
This results in a list of the most commonly used git commands.
There
is also a graphical git repository browser named gitk
that is written in tcl/tk. It displays changes in a
repository or a selected set of commits. This includes visualising
the commit graph, showing information related to each commit, and the
files in the trees of each revision.
The git commands that will be described in this chapter are:
help
- Display help information about git.
config
- Gets and sets repository or global options.
clone
- Clone an existing repository into a new directory.
status
- Shows the current status of the working directory.
init
- Create an empty local git repository or reinitialise an existing one.
add
- Add a working file to the staging area.
commit
- Commit a snapshot of the current staging area to the repository. Committing creates a new revision which can be retrieved later, for example if you want to see or work with the source code of an older version.
branch
- List, create or delete branches.
log
- Shows commit logs.
checkout
- Checkout a snapshot (usually a branch or path) to the working directory.
merge
- Merge changes from named branch into current branch.
rebase
- Forward-port local commits to the updated upstream head.
clean
- Remove untracked files from the working directory.
tag
- List, create, delete or verify a tag object.
remote
- Manage the set of remote repositories (“remotes“) whose branches you track.
fetch
- Download objects and refs from another repository.
pull
- Fetch and merge.
push
- Push local changes to a remote repository.
apply
- Apply a patch to files.
format-patch
- Create and format a patch for submission.
Getting help
Without any arguments or options, the git help
command just prints a list of the most commonly used git
commands on the standard output. However the help
command
accepts options and arguments. Try the following versions of the
help
command:
$ git help --all $ git help status
In the first example, the --all
option is used to produce
a list of all git commands (not only the most commonly used).
In the second example help information about another git
command (status
) is requested.
This chapter only scrapes the surface of using git in the Drupal environment, but I hope it is enough information here to get you started. For a gentle introduction to key concepts, read Tom Preston-Werner's The Git Parable. For a much more comprehensive description of what you can do with git, see Git Magic by Ben Lynn or GitHowTo by Alexander Shvets. The closest thing to an official git manual is Scott Chacon's book Pro Git book. For more information, see the page on Drupal.org listing other git resources and the Drupal git documentation.
Setting up
The CLI command git config
lets you
configure git. There are three options that specify what
configuration file to interact with:
--system
. File:/etc/gitconfig
. Settings apply to all users and repositories.--global
. File:.gitconfig
in users home directory. Settings apply to current user.--local
. File:.git/config
in repository. Settings apply to current repository only.
You must identify yourself to each installation of git you use (home, office, laptop, etc).
Here is an example of the necessary commands to establish identity:
$ git config --global user.name "John Smith" $ git config --global user.email johnsmith@example.org
You should of course use your own real name and email-address. If you have created an account at Drupal.org, the recommended version of these two commands can be found at
.You'll only have to do this once at each device.
You may also define global aliases for common commands by
editing .gitconfig
directly. This alias outputs the
history of the brach on a compact format:
[alias] hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
After registering yourself as a git user at Drupal.org, upload your public ssh key.
.If you don't have a public ssh key, learn how to create it in Unix notes.
To check your current local configuration, use this command:
$ git config --list
Unless you have established a global configuration, it will not return anything if you're not inside a local git repository.
If you use it inside a local git repository, it will also list values pulled form the the repository configuration . This means that the output may be different if run from a inside a repository, compared from the output you get if it is run outside a repository.
If you have different user names and different remotes, it is recommended to configure each repository with specifics.
Creating a project
There is two ways to create a local git project.:
- Clone an existing (usually remote) git repository.
- Make an existing local directory a git project.
Cloning a repository
If you want to get a copy of an existing git repository you do so with the command clone. Note that the clone command pulls down nearly all the data in the repository.
For instance, to clone a copy of the repository for the Gateway skeleton for Grav from GitHub, you use one of the following commands:
$ git clone git://github.com/getgrav/grav-skeleton-gateway-site.git $ git clone --branch master git://github.com/getgrav/grav-skeleton-gateway-site.git
The first checks out the default branch, the second checks out a specific branch.
With a few exceptions, Drupal project repositories are, with a few exceptions (drush is one of them) not hosted on GitHub. Instead, they're hosted on git servers provided by the Drupal Association.
If you want to work with existing Drupal code, perhaps to make a temporary bug-fix to the core or contributed part of your Drupal site without having to wait for the bug fix to appear in the project on Drupal.org, you should start by cloning (downloading) the relevant branch of the Drupal project you're interested in from a remote Drupal project server.
A branch is (usually a a succession of) committed source code snapshots with its own history. The branch pointer points to the most recent of these. Also note that while master branches are commonly used in the git world, the Drupal community uses version branches (e.g. 7.x-1.x, 1.0.x) instead. This is because a Drupal repository contains branches for several major versions of Drupal, and “master” does not indicate what Drupal version the project is compatible to.
For example, if you are one of maintainers of the Notify project, you may clone its repository and have its head pointing at the 2.0.x branch, with the following command:
$ git clone --branch 2.0.x git@git.drupal.org/project/notify.git
If you are not one of the project's maintainers, use this one instead:
$ git clone --branch 2.0.x https://git.drupalcode.org/project/notify.git
You will find the right clone-comamnd to use under the project's
tab on Drupal.org.The clone command creates a directory named
notify
, initialises a .git
directory inside
it, make a local copy of that repository. If the branch option is
included on the command line, it does a checkout of a working
copy of the latest version of that particular branch. If you go into
the new notify
directory, you'll see that all the project
files are there, ready to be worked on.
Note that you can also check out a particular branch in a separate step after you've cloned the repository. The three commands below accomplish that.
$ git clone git@git.drupal.org/project/notify.git $ cd notify $ git checkout 2.0.x
The main tool you use to determine the status of your files is the
status
command. If you run this command directly after a
cloning a 2.0.x branch, you will see this:
$ git status # On branch 2.0.x nothing to commit (working directory clean)
This tells you what branch you're on (here 2.0.x). It also tells you that you have a clean working directory. By “clean” git means that there are no files in the working directory that is not tracked, and there are no tracked files that has been modified but not committed.
If you want to clone the repository into a directory named
something other than notify
, you can specify that
as the next command-line option like this:
$ git clone --branch 2.0.x git@git.drupal.org/project/notify.git mynotify
The clone
command can also be used locally and
recursively, but this will not be discussed in this ebook.
Initialising a local repository
If you're already working on a project, but want to start tracking it with git, you want to initialise a local git repository to work with.
You do this by using the following command in the directory where you keep the project's files:
$ git init
This creates a default branch named master and a new
sub-directory named .git
. This sets up a skeleton git
repository. Nothing is tracked yet, but the framework for doing so is
in place.
If you check the status at this point, you will be told that all the files in the working directory is “untracked”. Example:
$ git status # On branch master # # No commits yet # # Untracked files: # (use "git add..." to include in what will be committed) # # README.md # fancy.info # fancy.install # fancy.module nothing added to commit but untracked files present (use "git add" to track)
The next thing to do it to is make sure the files are tracked by
git by adding them to the staging area. You can accomplish
that with a few git add
commands that specify the files you
want to track. E.g.:
$ git add README.txt $ git add fancy.*
If we check the status after doing this, we see that the status of the files have changed:
$ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached..." to unstage) # # new file: README.txt # new file: fancy.info # new file: fancy.install # new file: fancy.module #
Committing to a local repository
At this point, you are able to use the commit
command to
commit a snapshot of the current state of the staging area to the
repository. You need to supply a message. If you use the -m
option to input the message on the command line, it should be a single
line of text that identifies the snapshot. E.g.:
$ git commit -m "Initial version of the fancy project"
(If you do not use the -m
option, git will
launch a text editor and prompt you for the message to include.)
To learn more about commit messages, you may want to read the Drupal.org guidelines for commit messages, Writing good commit messages, and 5 Useful Tips For A Better Commit Message. Note that while single line commit messages are discouraged in some of these documents, single line commit messages are welcome in the Drupal community.
If we review the status after making this commit, we're back to a state were we have a clean working directory:
$ git status # On branch master nothing to commit (working directory clean)
If this is a Drupal project you should change the branch name from “master” to something that indicate what main version of Drupal the project is compatible with. Use the legacy version branch (e.g. 7.x-1.x) or semantic version branch (e.g. 1.0.x):
$ git branch -m master 1.0.x $ git status # On branch 1.0.x nothing to commit (working directory clean)
If you want to skip the step where you add files you're already
tracking the staging area, there is a shortcut. The -a
option to the git commit
command makes git
automatically stage every file that is already tracked before doing
the commit. E.g.:
$ git commit -am "Second commit"
Making minor changes
Sometimes, you want to amend a commit. For instance, let us say that you make a mistake when typing in the commit message:
$ git commit -m "Removed cpnfusing URLs"
In that case, you can remove the error by committing again, using
the --amend
option to amend the previous commit:
$ git commit --amend -m "Removed confusing URLs"
If you also want to add a file you've changed after you've committed:
$ git commit --amend -am "Updated CHANGELOG.txt"
The --amend
option makes a new snapshot of your staging
area and use it, as well as the new message, to replace the last
commit. If you've made no changes to the staging area since your last
commit, then your snapshot will look exactly the same and all you'll
change is your commit message. If you've made changes to your staging
area between the initial commit and the amend (say you forgot to add a
file and fixed that) then the amended commit also changes the snapshot
to reflect the changes in your staging area.
You need to be careful because amending changes the SHA-1 of the commit. Don't amend your last commit if you've already pushed it.
Basic local workflow
We'll get around to distributed use of git eventually, but first, let us only consider how git appears in a local context. git knows about three local storage areas:
- The repository (sometimes referred to as the git directory) where committed files are kept.
- The staging area (sometimes referred to as the git index), where files ready to be committed are kept.
- The working directory (sometimes referred to as the working tree), where you keep modified files as long as you are working on them.
Corresponding to the three storage areas are three states that a file may be in: committed, staged, or working:
- Committed means that a current snapshot of the file is safely stored in the repository.
- Staged means that the file is a working file is marked to be committed, but not yet committed.
- Working means that the file is checked out of the repository, is in the working directory, and is worked upon (i.e. edited).
Whether you've cloned a remote repository or initialised your own local repository, you should now be set up to use git for version control. But before doing so, you need to understand branches.
A branch is a named pointer into the repository. A branch consists of a succession of commits with its own history. Also, to know what branch you're currently on, git keeps a special local pointer called HEAD.
The idea behind branches is to allow you to create a new branch from an existing one and change the code within this branch independently from other branches. This makes it simple to go back to a previous branch if the new branch did not work out. It also allows teams to work in parallel on independent branches.
To work on a particular branch, the developer performs a checkout. This makes the working directory contain the snapshot that was committed for that particular branch. After checking out a branch, the files in the branch may be edited. When done, the changed files are added to the staging area and committed, as illustrated with the diagram below:
The basic git local workflow goes something like this:
- You checkout the branch you want to work with from the repository.
- You edit the checked out files in your working directory.
- When done, you stage the files, adding snapshots of them to your staging area.
- Finally, you do a commit, which takes the edited files as they are in the staging area and stores that snapshot permanently in the repository.
After storing the snapshot in the repository, the developer can move on to working on another branch, knowing that the committed branch can be checked out again any time.
Now, let us look try this out in practice, using the fancy project introduced as an example.
We pick up the project just after making the initial commit. To see the status, current branch, and a log of all events, use the following three commands:
$ git status # On branch 1.0.x nothing to commit (working directory clean) $ git branch * 7.0.x $ git log commit 3e96d03a97591dedc0603223aad3ed1c2392a56f Author: Bob SmithDate: Fri Oct 12 19:08:44 2012 +0200 Initial version of the fancy project.
The branch
command tells us that there is only one
(local) branch, and that it is named 1.0.x. The asterisk
(*
) to the left of the branch name indicates that this is
the current branch.
The log
command lists each commit with its SHA-1 hash,
the author's name and email, the date written, and the commit
message. The SHA-1 hash can be used later to identify the commit.
The named marker for a branch (usually referred to as the “branch pointer” will always point to the latest commit for the branch. This means that the 1.0.x branch pointer will move ahead when we change the code in this branch and commit them.
To illustrate how the branch pointer moves, we're going to create a new branch named “initial”, and then list the local branches:
$ git branch initial $ git branch initial * 1.0.x
Note that the current branch is still 1.0.x (as indicated
by the *
next to the branch name), but now an additional
branch named initial also exists. At the moment, both branch
pointers point to the same snapshot.
Now, go ahead and make some changes to the files in the working area. When done, do another commit:
$ git commit -a -m "Second commit"
This second commit moves the current branch pointer (1.0.x) and HEAD forwards, so we now have a branch tree that looks like this:
There are now two branches, one named initial pointing to the initial commit, and another named 1.0.x that has moved on to the latest (second) commit. This is how the log now looks like.
$ git log commit 391a7317504b970adca391e9a85bab1f59d713cb Author: Bob SmithDate: Fri Oct 12 19:40:04 2012 +0200 Second commit. commit 3e96d03a97591dedc0603223aad3ed1c2392a56f Author: Bob Smith Date: Fri Oct 12 19:08:44 2012 +0200 Initial version of the fancy project.
Now, if we want to use initial as a starting point for another branch, we only have to check out the initial branch from the repository.
$ git checkout initial
This results in the following position for the branch pointers, where HEAD points to the current branch:
And just to make sure that the current branch is initial:
$ git branch * initial 1.0.x
Now, the initial snapshot will be your starting point when you edit files and add them to the staging area.
The checkout
command accepts arguments that are not branch
names. For instance, you can checkout snapshots not associated with a
branch by referring to them with an abbreviated version of their SHA-1
hash. You need to supply enough of the SHA-1 hash to make it unique,
and at least four characters. Also, by adding the -v
option
to the branch
command, we get to see the first six digits
of the SHA-1 hash and the commit message of the last commit on each
branch. Example:
$ git checkout 391a Previous HEAD position was 3e96d03... Initial version of the fancy project. HEAD is now at 391a731... Second commit. $ git branch -v * (no branch) 391a731 Second commit. initial 3e96d03 Initial version of the fancy project. 1.0.x 391a731 Second commit.
When you develop a project, and make new commits, the current branch pointer will advance and point to new snapshots identified by their SHA-1 hash. You can depend on the SHA-1 hash as a permanent and unique identifier of a particular snapshot.
If you go through these steps, the SHA-1 hash created for your branches will be different from the hash used in the examples in this chapter. There is no meaning to the SHA-1 hash. It is only a persistent identifier of a specific snapshot.
If you want to preserve this snapshot as a branch, you can now go
ahead and create it as a new named branch. The -b
option to
the checkout
command both creates a new named branch, and
also makes it the current branch.
$ git checkout -b second $ git branch -v initial 3e96d03 Initial version of the fancy project. 1.0.x 391a731 Second commit. * second 391a731 Second commit.
The diagram below shows where the branch pointers are at this point:
Now, checkout the 1.0.x
branch again, make some changes
and commit it again with message “Beyond second”. This
moves the 1.0.x
branch forwards. However, since the
second
branch is not active, it remains pointing at
the same snapshot.
Listing the branches now should produce something like this:
$ git branch -v initial 3e96d03 Initial version of the fancy project. * 1.0.x 20782e3 Beyond second. second 391a731 Second commit.
Let's say you want to try out some alternative idea, so you go back
and check out the initial branch again, make some changes, create a
new branch name alt
, commit that, and finally checkout the
1.0.x
branch again. Listing the branches gives something
like this:
$ git branch -v alt b65c49a Alternative initial 3e96d03 Initial version of the fancy project. * 1.0.x 20782e3 Beyond second second 391a731 Second commit.
And this is a picture of the branch tree at this point.
We now have two alternative branches. Both have the branch named
initial
as their starting point, so some files and text is
probably shared between the two. But some of the material may also
be different versions of the same thing.
Useful checkout commands
To get to the most recent commit of a specific branch, use the name if the branch, e.g.:
$ git checkout 1.0.x
To get back to your previous HEAD:
$ git checkout -
To see a log of your most recent checkouts:
$ git reflog
Merging branches
When developing a project along alternative branches, we may want
to integrate changes from one branch into another. This is done with
the command merge
.
The command merge
will merge the named branch with the
current branch. To merge the alt branch into the
1.0.x branch do the following:
$ git checkout 1.0.x $ git merge alt Merge made by recursive. MANIFEST.txt | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+), 0 deletions(-)
Given the branch tree we've been working on, this is how it looks like after this merge:
Sometimes, the merge process is not fully automatic, and git will report a merge conflict. In that case, git will suspend the merge to allow you to resolve the conflict before proceeding.
The standard way to integrate changes from one branch into another
is done with the merge
command as shown above. It
performs a three-way merge between the two latest branch snapshots
(20782…
and b65c4…
) and the most recent common
ancestor of the two (3e96d…
), creating a new snapshot, and a
commit (f6f9a…
).
However, there is a slightly different route to merging where you
use the command rebase
before doing the merge. If there are
no conflicts, plain merge
, and rebase
followed by
merge
, produces the same result, but the latter makes the
history look “cleaner”, pushing the alternate branch ahead
of the 1.0.x and the fast-forwarding the 1.0.x pointer to it. This
makes it appear as if all changes were committed linearly
To do this, first use this pair of commands makes it appear as if the alt branch pointer is ahead of the 1.0.x branch pointer in the branch tree:
$ git checkout alt $ git rebase 1.0.x
The go back to the 1.0.x branch, and then merge the alt branch. This merge will fast-forward the 1.0.x branch to incorporate the changes in the alt branch.
$ git checkout 1.0.x $ git merge alt
If there are conflicts, using rebase
usually requires
extra work to resolve those conflicts, and a standard merge
may offer a better route.
Ignoring files and directories
Placing names of files and directories in a special file
named .gitignore
to make git not add them to the
index. It has no effect on files that are already tracked.
To stop tracking a file you need to remove it from the
index. Example, to ignore a NOTES.txt
that is
tracked:
$ git rm --cached NOTES.txt
If you want to remove a whole folder, you need to remove all files in it recursively:
$ git rm -r --cached badfolder
If you download a repo that is set up to
track .gitignore
it probably applies to another
developers local repo. Best practice is not to
track .gitignore
. To get make git completely
forget about it, first add it to the list of files to ignore in you
local .gitignore
-file. Then:
$ git rm --cached .gitignore $ git add . $ git commit -am "Do not track .gitignore"
Deleting a branch
Provided the branch has been merged, to delete a local branch named “second”, use the following command:
$ git branch -d second
If the branch has not been fully merged, this will result in an error message. If you don't care about the code in this branch, and just want to delete it, use:
$ git branch -D second
This turorial hasn't discussed remotes yet, but for
future reference, to delete a remote branch, use
the push
command and prefix the name of the branch to
delete with a colon (:
).
$ git push origin :second
You'll not be allowed to delete a remote branch if it is the default branch, so make sure you change this before doing a delete.
Discarding changes
There may be day when you hack before having coffee. Don't!
But if you've done this, and you haven't committed your changes yet, you can get back the last committed version of any file. For instance, to rollback “myfile.php”, you can use the following command:
$ git checkout -- myfile.php
To revert your entire working directory and staging area to the last commit, use following two commands:
$ git checkout . $ git clean -df
The first command reverts the tracked files in the staging area to the last commit by discarding all changes to tracked files. The second command deletes all files that are not tracked from the working directory.
If you want to undo the previous commit;
$ git reset --soft HEAD~1
The command reset
will roll your
current HEAD
branch back to the specified revision. In
the example above, the latest commit is discarded – effectively
making the last commit undone.
The flag --soft
makes sure that the changes in undone
revisions are preserved. After running the command, the changes will
exist as uncommitted local modifications in the working
directory.
If it is not necessary to retain these changes, use
the --hard
flag.
$ git reset --hard HEAD~1
If you want to undo a bad commit you can revert it:
$ git revert 41767c3
Unlike reset, it preserves the bad revert, but adds a new HEAD where whatever it did was removed. I.e. it does the equivalent of reset, followed by subsequent recommits of subsequest commits.
Source sonic 002: git reset vs git revert.
Squashing commits
Let us say you've made a few tiny commits, and before you push your work, you want to make to merge them into a larger commit. For instance, let's say your repository looks like this.
$ git log --pretty="%ad %h %s" --date=short 2014-06-06 62e2757 Fixed yet another typo. 2014-06-06 0359fd5 Added missing word to one of the strings. 2014-06-06 5fbc028 Fixed a misspelling. 2014-06-06 20c1b45 Removed a duplicated word. 2014-06-04 1d7d1af Added tag for 7.x-1.0-alpha1 release. 2014-06-02 c7c02f7 Corrected a serious error in the database schema. 2014-05-30 d802ac2 Initial commit
The last four commits are just trivial typographical corrections made to different strings. It may be a good idea to squash these into a single commit. The simplest way to do this is to use the rebase command in its interactive mode. The command for this is:
$ git rebase -i HEAD~4
This should fire up your editor with the following contents.
pick 20c1b45 Removed a duplicated word. pick 5fbc028 Fixed a misspelling. pick 0359fd5 Added missing word to one of the strings. pick 62e2757 Fixed yet another typo. # Rebase c7c02f7..62e2757 onto c7c02f7 # # Commands: # p, pick = use commit # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted.
Edit the first four lines to look like this:
pick 20c1b45 Removed a duplicated word. squash 5fbc028 Fixed a misspelling. squash 0359fd5 Added missing word to one of the strings. squash 62e2757 Fixed yet another typo.
Basically this tells git to combine all four commits into the the first commit on the list. Once this is done and the text buffer saved, the editor fires up again, showing you the commit messages to all the commits involved:
# This is a combination of 4 commits. # The first commit's message is: Removed a duplicated word. # This is the 2nd commit message: Fixed a misspelling. # This is the 3rd commit message: Added missing word to one of the strings. # This is the 4th commit message: Fixed yet another typo.
Since we're combining commits, git let you create and save new commit message for the combined commit. Edit the message as you see fit, delete or comment out the messages you no longer want. Then save the buffer and quit. Once that is done, and your commits have been successfully squashed, and you should see a message like this:
[detached HEAD 6ddba5d] Corrected several typos in various strings. 4 files changed, 7 insertions(+), 7 deletions(-) Successfully rebased and updated refs/heads/1.0.x.
If you run into conflicts during the rebase, they're usually quite
easy to resolve. However, if you get lost, the command git
rebase --abort
will bring you back to your previous state.
Only rewrite history with rebase if the commits haven't been pushed an external repository. If others have based work off of the commits that you've deleted, plenty of conflicts will occur. It is important that you do not rewrite the history if it has been shared with others.
Differences
We have already encountered the
status
command. This command will
tell you the name of the files that have changed, but no details about
these changes.
To see the changes in more detail, there is the git
diff
command. This command is used to see the
differences between two different points in the tree. For example to
see how two branches differ, or to see what has changed from one
commit to another.
The examples below are just the most common uses of
diff
. The command is very versatile and let you use a
lot of different arguments to specify what to compare.
The most basic version of the diff
command shows the
differences between the files that have changed in the working
directory and the staging area for the next commit.
$ git diff
Please note that git diff
alone doesn't show all
changes made since your last commit. It shows only the
changes that are still unstaged. This means that if you've staged all
of your changes, git diff
will give you no output.
To see the differences between the files that have changed in the working directory and the most recent commit (whether they're staged or not), you can use the following variant of the command:
$ git diff HEAD
This will show the difference between the current working directory
and the HEAD
of the current branch committed to the
repository.
The following two commands are equivalent and will show the differences between the HEAD of the current branch and its parent:
$ git diff HEAD^ HEAD $ git diff HEAD~1 HEAD
To see the differences between the any pair of commits you can specify a numeric offset relative to the current HEAD like this:
$ git diff HEAD~4 HEAD~2
To see a summary with the names of the files changed and the type
of change, you can use the argument --name-status
.
This argument will suppress the actual diff output. Example:
$ git diff HEAD^ --name-status
To see the differences between a specific file in another branch and a file current working directory, use this syntax:
$ git diff bugfix:myfile.php myfile.php
This will show the differences in the file named “myfile.php” in the branch named “bugfix” and the same file in the current working directory.
To recursively compare two directories that is managed
with git without comparing the contents of .git
,
use one of the following commands:
$ diff -rq --exclude=.git a/ b/ $ diff -rq -x .git a/ b/
Viewing the past
To view an old version of a file, without checking it out from the
repository, use the show
command followed by a revision
specification followed by the path to the file-name. E.g.:
$ git show HEAD~4:tests/pagenotfound.test $ git show dae86e:tests/pagenotfound.test
Note that the path must start from the root of the repository.
Tags
Like other version control systems, git has the ability to tag specific points in history as being important. In Drupal, this functionality is specifically used to mark release points for contributed projects (e.g.: 7.x-1.0), which indicates the first official release of the Drupal 7 version of the project.
Two types of tags exist: lightweight and annotated. Lightweight tags are meant for private or temporary object labels, while annotated tags are meant for public releases.
I.e.: Lightweight tags are just bookmarks. They help you locate and go back to a specific point in history. Annotated tags tells you who created them, and sometimes also why. In a large project, that my be important information.
To create a lightweight tag “1.0-alpha
”:
$ git tag 1.0-alpha
To create an annotated tag, use the option -a
. When you
create an annotated tag, you must also create a message that describes
the tag. Example:
$ git tag -a 7.x-1.0 -m "Release of my first version for Drupal7!"
Tags are listed alphabetically, not chronologically. To list all tags present in the current repository, type:
$ git tag … 7.x-1.0 7.x-1.0-alpha1 7.x-1.1 7.x-1.10 7.x-1.11 7.x-1.12 7.x-2.0 …
To look up information about a tag, use the show
command. Example:
$ git show 7.x-1.12 tag 7.x-1.12 Tagger: webchickDate: Wed Feb 1 14:04:15 2012 -0800 Release 7.x-1.12 commit 4d4080b17681ae674e10c077b72d00f0b1544e0c Author: webchick …
This is an annotated tag, because it starts with
a line starting with Tagger:
that identifies
the person that created the tag. A lightweight tag
starts with the line commit
.
You may check out a tagged snapshot:
$ git checkout 1.0-alpha
As always when you do not check out a named branch, you're put in a state with a detached HEAD. This is fine for experimentation, but if you want to retain the commits you make in this state, you must also create a new branch.
To delete a tag, use the option -d
:
$ git tag -d 1.0-alpha
Do not delete tags pushed to Drupal.org.
Interacting with remotes
When you clone a remote repository, git automatically associates with the short-name “origin” to indicate that this is the original repository where the branch resides.
If you don't start out by cloning a remote directory, but instead want to create a remote origin based on a local git repository, you use the git command “remote add origin”. Example:
$ git remote add origin git@git.drupal.org:sandbox/bob/123456.git
The above command will assocate the drupal.org sandbox “git.drupal.org:sandbox/bob/123456.git”, owned by “bob”, with “origin”.
If there is already a value associated with “origin”, and you want to replace with another, you use this instead:
$ git remote set-url origin git@git.drupal.org:sandbox/bob/123456.git
In addition to the default (origin) you can associate any
other short-name with a remote repository with the
remote add
command. Example:
$ git remote add notify git@git.drupal.org/project/notify.git
The command remote
alone lets you see which remote servers
you have configured. Adding the -v
option also shows the URL
that is associated with the short-name:
Here is an example of output from this command after cloning the main branch of the notify project and explicitly associating the short-name zink with the same remote repository.
$ git remote -v origin git@git.drupal.org/project/notify.git (fetch) origin git@git.drupal.org/project/notify.git (push) zink git@git.drupal.org/project/notify.git (fetch) zink git@git.drupal.org/project/notify.git (push)
If you want to see more information about a particular remote, you can use the
command remote show
, and specify the short-name of the remote you are
interested in. This example shows the result of running this command after we've
cloned the repository for the Drupal notify project.
$ git remote show origin * remote origin Fetch URL: git@git.drupal.org/project/notify.git Push URL: git@git.drupal.org/project/notify.git HEAD branch: master Remote branches: 3.0.x-1.x tracked 4.0.x-1.x tracked 4.1.x-1.x tracked 4.2.x-1.x tracked 4.3.x-1.x tracked 4.4.x-1.x tracked 4.5.x-1.x tracked 4.6.x-1.x tracked 4.7.x-1.x tracked 5.x-1.x tracked 5.x-2.x tracked 6.x-1.x tracked 7.x-1.x tracked drop tracked master tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date)
Without any options, the branch
command will list
branches that has been checked out locally. With the option
-a
, both local and remote tracking branches will be
listed. The option -v
produces a verbose format, where
the first six digits of the SHA-1 hash and the commit message of the
last commit on the branch is shown. Example:
$ git branch -a -v * 7.x-1.x 27d89bb Issue #1159632 initial port to D7 remotes/origin/3.0.x-1.x 174d865 notify.module - fixed minor v3.00 bug. remotes/origin/4.0.x-1.x 0a7488c Stripping CVS keywords remotes/origin/4.1.x-1.x cb25868 Stripping CVS keywords remotes/origin/4.2.x-1.x fa0f8cd Stripping CVS keywords remotes/origin/4.3.x-1.x 2fc981d Stripping CVS keywords remotes/origin/4.4.x-1.x 2a3af64 Stripping CVS keywords remotes/origin/4.5.x-1.x 69d95ad Removing translation directories remotes/origin/4.6.x-1.x 48f72ce Removing translation directories remotes/origin/4.7.x-1.x 6d40564 Removing translation directories remotes/origin/5.x-1.x 3e44f63 Removing translation directories remotes/origin/5.x-2.x f3a8465 Removing translation directories remotes/origin/6.x-1.x 3c5f64b Removing translation directories remotes/origin/7.x-1.x 27d89bb Issue #1159632 initial port to D7 remotes/origin/HEAD -> origin/master remotes/origin/drop 27a0d5c This commit was manufactured as part … remotes/origin/master f5bdc14 Removing translation directories
Even if a particular branch is only listed as “remote”, it is also present in the local repository, and checking it out will make it appear in the local working directory.
$ git checkout 6.x-1.x Branch 6.x-1.x set up to track remote branch 6.x-1.x from origin. Switched to new branch '6.x-1.x'
Synchronising with a remote
Git does not automatically check the remote when reporting status. The following high level command will bring all your remote referenes up to date.
$ git remote update
You may also use the fetch
command to do the same thing. For example, to fetch from the origin, you do:
$ git fetch origin
This operation does not change any of your own branches and is safe to do without changing your working directory. It just makes sure that named heads and tags present in the remote repository, and the associated objects, is present in the local repository.
Using the status
command after a remote update or
fetch will then tell you about any differences between your local and
the remote repository:
$ git status -u no
If the status message tells you that you are behind the remote, and
that the branch can be “fast forwarded”, this means that pulling down
the necessary commits from a remote branch and merging those into the
current local branch will not create any conflicts. In that case, all
that is required to synchronise your local copy of the branch with the
remote, is to pull
. I.e.:
$ git pull origin
Which, btw., is equivalent to:
$ git fetch origin $ git merge
At some point in the project, there will be a time when you want to
make your changes available on a development snapshot on the Drupal
project remote server. This is what the push
command is
for. Adding the -u
option makes sure that the local
branch track the remote branch. So to push the changes on the local
1.0.x branch to a development snapshot on origin, commit
your changes and use the following command:
$ git push -u origin 1.0.x
To make development snapshots pushed to the repository on the Drupal project remote server downloadable from the project page, click on the link
near the bottom of project's project page on Drupal.org. Checking the box below “Show snapshot release” will automatically package the snapshot into a tarball after it has been uploaded, and then link to it under “Downloads” on the project page. Packaging of snapshots may take up to 10 minutes, so there may be a delay before a packaged snapshot appears on the project page.The other settings on this page control of tagged releases are shown (or not) on the project page.
If a local branch you want to share does not already exist on the remote repository, just pushing it will not work. You need to specify the tag on the command line. Example:
$ git push 1.0.0-alpha1
This will create the release 1.0.0-alpha1 at the remote, and push to it.
Drupal.org enforce certain Release naming conventions. If a release is tagged with a tag that does not follow the convention, such as "1.0.0-alpha-1" (unwanted hyphen), you can push it, but it will not show up when you try to create a new tagged release. You may also want to read DSE: What does rc stand for? It provides some guidance about the use of these tags.
See also DO: Creating a Project Release.
To create a tagged full release, you first have to tag the current HEAD with the release name, then push it as follows:
$ git tag 1.0.0 $ git push origin tag 1.0.0
To make the new tagged release downloadable from the project page, click on the link
near the bottom of project's project page on Drupal.org. This takes you to a page with a roll-down menu that lets you select the tagged release to add. [XXXX add screenshot]Pushing is only allowed if nobody else has pushed updates to the branch upstream since you cloned from the origin. If the remote repository is updated with somebody else's work, you need to pull their work first and merge it with yours before you will be allowed to push.
There is also a command to delete a remote branch, as described above.
Multiple remote users
To have multiple remote users sharing the local account, set up
host aliases in ~/.ssh/config
with a distict private
key IdentityFile
for each remote identity.
Below is an example of the entries for two users named "bob" and "fred" on the same server having different remote accounts:
# bob Drupal.org account. Host bobhost HostName git.drupal.org User git IdentityFile ~/.ssh/id_rsa # fred Drupal.org account. Host fredhost HostName git.drupal.org User git IdentityFile ~/.ssh/id_rsa_fred
Make sure the private keys given as IdentityFile
exists.
A project maintained as "bob" can now be cloned with the following command:
$ git clone --branch 1.0.x git@bobhost:project/example.git
Collaboration
If the question is: “How do I collaborate and share code with other developers?”, the best answer is git.
However, there are two basic ways of using git to share code: pull requests (aka. merge requests) and pathches. GitHub uses pull requests. On Drupal.org, patches has traditionally been used, but in 200, a variation of pull requests, called merge requests was introduced. All these are described below.
GitHub pull requests
On GitHub.com, you create a pull request to propose and collaborate on changes to a repository. Unless you've already has write access to the repository, you first need to fork the project on GitHub, and then create a feature branch to hold your committed changes.
- Fork the project you want to contribute to to your own GitHub repo (see Clone or download for URL).
- Clone a working copy to your local repo and create a new branch (
git checkout -b branch
). - Change, commit and push your version to your fork on GitHub.
- Create a pull request by going to the compare page and click Create pull request.
Note that changes are always proposed in a separate branch, to ensure that the main branch is kept clean and tidy.
The GitHub pull request is a very nice collaboration tool. A request immediately spawns a discussion where all comments, code reviews and followup activity of the request are captured chronologically as they happen. To learn more about pull requests, see the following pages on GitHub.com: Collaborating, Creating a pull request, Using pull requests.
GitLab CI
The Drupal community is now recommending that contributors leverage on GitLab CI framework by Using GitLab to Contribute to Drupal.
Contributions submitted as merge requests make it easy to inspect requested changes in GitLab and to commit them directly to the repo (i.e. without testing them locally). Ideally, testing should be done using GitLab's CI framework. Take care if there are no (or inadequate) server side CI unit tests.
See DO: Issue forks & merge requests, Reviewing and merging merge requests.
Creating a merge request
An issue fork is a temporary repository for working on source code changes for an issue. It starts out as a copy of the main repository for the project, but it allows all community members to commit and push changes. There can only be one issue fork per issue.
To create a new merge request press "Create an issue fork".
To see what commands you can use click "Show commands".
Reviewing and merging a merge request
Merges requests appear below the issue summary. The link to a merge request will be something like "MR !X mergeable", where X is an integer. If a request is mergeable, a button with the label "Merge" when you click the link to the merge request (if it don't appear, make sure you're logged in to GitLab as someone who are allowed to write to the repo). When you click this button, the merge will be committed to the Drupal repo, making it ahead of your local repo. To synchronise, your local repo, use the following pair of commands:
$ git remote update $ git pull
AFAIK, there are no instructions for how a non-maintainer can merge in a merge request into their local copy of the repostitory. I've asked for expanded instructions in this forum post. The reply links to instructions about how to review a merge request in GitLab UI which is not relevant to my use case.
However, this works: To create a file that can be use to patch the code base for testing locally, click "plain diff" adjecent to the MR, and save the resulting page as a patch-file.
Drupal.org patches
On Drupal.org, a patch posted to a project's issue queue used to be the standard method to propose and collaborate on changes to a repository. They are now being replaced by merge requests to leverage on GitLab's CI testing framework.
A patch is a structured file that consists of a list of differences between one set of files and another. For a general introduction to patches in a Drupal context, see the Drupal community page about patches. Note that the Drupal community have strict requirements for submitted patch files that are intended to be used for official Drupal projects. For a detailed description of how to create a patch for a Drupal project, see the advanced patch contributor guide.
Applying a patch
A common task for a Drupal site admin is to install hot-fixes on the site. For instance, the latest release of some module your site depends on is badly broken, and no new release has yet been made officially available. Sometimes, when this happens, some enterprising individual has made a patch that addresses the problem available. How do you apply that patch to your configuration?
With git, you first must identify what branch the patch apply to. This is usually stated up-front by the supplier of the patch. If not, ask.
Then, you start out with making a clone of the branch you want to work with. This creates a local copy of the branch on your local machine. For example, to make a local clone of the 1.0.x branch of the Drupal Notify project, you can use the following command:
$ git clone --branch 1.0.x http://git.drupalcode.org/project/notify.git
Now you can go ahead and download the patch. If the file
containing the patch is named notify-wrong_type-1159632-22.patch
.
the following git command will apply it:
$ git apply -v notify-wrong_type-1159632-22.patch
The -v
switch just makes the output verbose. Omitting it
makes git, like most Unix and Gnu/Linux programs, silent if things go well.
Creating and posting a patch
First, if you plan to make a patch, make sure that you work on a local branch with a different name than the branch cloned from the origin. For instance, if you cloned branch 1.0.x, switch to a local branch named myfix before commiting any changes:
$ git checkout -b myfix
After switching to a new, local branch, you can start working on the code, making as many changes as is necessary to fix a bug or add a feature. When you're satisfied with the result, you may want to share it with the Drupal community. To do this, you need to create a patch and post it in the appropriate issue queue on Drupal.org.
Patches
are identified by issue number. Patch file names also
contains a comment number. The issue number is simply the
node ID for the node on Drupal.org where the issue is
described. For instance, given the following node:
https://www.drupal.org/node/1159632,
the node ID is 1159632
. Therefore, the issue number is “1159632”.
The comment number is the comment number the patch file will be attached to
when you upload it. It is one more than the last posted comment in the
issue thread.
First make sure that the commit message mentions the issue number
the patch is supposed to fix, and your name. If you didn't do this
when you made the commit, you can use the amend
option to the
commit
command to fix this. Here is an example:
$ git commit --amend -m "Issue #1159632 by bob: Fixed crash due to wrong type"
If you're just a single commit ahead of the branch you forked from, the simplest way to create a patch is the following command:
$ git format-patch -1
The number option indicates the number of commits to create patches for, but if you specify more than one, you'll get a set of patches that must be applied in the correct sequence. The patch files will be named after the commit messages. You may need to change the names to match Drupal conventions.
The following naming convention for patch files has been adopted by the Drupal community:
[module_name]-[short-description]-[issue-number]-[comment-number].patch
If you want to create a patch for more than a single commit, something like the following command may be used:
$ git format-patch 1.0.x --stdout > notify-wrong_type-1159632-22.patch
It creates a single patch file that will the fast-forward the named branch (1.0.x in the example) to the head of the current branch. This means that anyone can clone the 1.0.x branch from the central repository, use git to apply your patch (as described in the previous section), and have a snapshot identical to yours.
Committing a patch
If you're the maintainer of a project hosted on Drupal.org, one of your duties is to commit patches written by others to the project's repository after the patch has been reviewed and tested by the community and found to be good.
If the patch you want to commit to Drupal's git repository is not your own code, but a patch created by somebody else, it is important that authorship is credited to that person (and not you). If you locate the patch in the issue queue at Drupal.org, expanding the field Credit & committing will suggest the git commit command to use:
You can edit the Commit message field to make the message more appropriate before copying the git command.
Sandboxes and full projects
If you want to be part of the special part the Drupal developer community that contributes code to the Drupal public code base, the first step towards learning the ropes is to set up a sandbox.
A sandbox project is a project that consists of experimental code. It may not yet be ready for public use, or it may just be an experiment never intended for public use. While sandbox projects reside on a public server that is very similar to the server hosting full projects, sandboxes comes without administrative hurdles and obligations of full projects. Anyone with git access on Drupal.org can set up a sandbox project and become that project's maintainer.
In addition to offering aspirant maintainers a place to learn how to maintain a public project, sandboxes is often used by experienced maintainers to get early versions of a project tested by a smaller part of the community before promoting the sandbox project to a full project.
If you've already created an account at Drupal.org and obtained git access, you're set up to create a sandbox project.
In your dashboard a Drupal.org, click on the tab
and then on the link . This takes you to a form that let you add a new sandbox project by filling in a formwhere you pick the project type and status, and provide a name and description for the project. In the examples in this section, I've used the name “My sandbox”.The remaining steps are outlined here.
After filling in the form and clicking
git@git.drupal.org:1234.git
.
When you do this, the origin will of course be different.
$ mkdir my_sandbox $ cd my_sandbox $ git init $ echo "name = my_sandbox" > my_sandbox.info $ git add my_sandbox.info $ git commit -m "Initial commit" $ git branch -m master 1.0.x $ git remote add origin git@git.drupal.org:1234.git $git remote add origin gitusername@git.drupal.org:sandbox/gitusername/1234.git$ git push origin 1.0.x
To set up a full project, replace 1234 with the project's shortname.
Here is, in plain English, a brief explanation of what these steps does:
- Create a local directory named
my_sandbox
. - Make this the current directory.
- Initialise a local git repository in it.
- Create a skeleton info-file.
- Add this file to the staging area.
- Commit it locally
- Rename the branch from master to 1.0.x.
- Associate the short-name origin with your remote (sandbox) repository.
- Push the project, and make the remote branch.
Note that in order to be able push, you must authenticate yourself to the remote server. Without authentication, someone else would be able to poison your remote repository with malicious code. The preferred way of authentication is with ssh keys. However, if you've not set up an ssh key in your Drupal.org account, git will prompt you to authenticate using your Drupal.org password.
After a sandbox project has been created, a link to it will appear under the tab
in your dashboard when you're logged in on your account on Drupal.org. You should be able to clone branches from it, and push new content to it.After the first push, the page accessible via the
push
).
But checking the box labelled
and clicking
will display a version of the page
better suited for a non-maintainer.
Becoming a git vetted contributor
https://www.drupal.org/node/1047190[TBA]
Promoting a sandbox
When you've obtained git vetted access, you will have the option to promote the sandbox project to a full project.
Demoting a project
It is also possible to demote a full project to a sandbox in order to archive it and repurpose its namespace.
In this example, the project named “Oldproject” is going to be demoted into a sandbox.
First, you need to create an empty sandbox. Sandboxes have numbers instead of characters in their short name. In this example, we will use “1234” for the short name, but you will need to use the actual number that is automatically assigned to your sandbox when you create it.
The screenshot below shows a typical sandbox that is beeing prepared to hold the archive of an obsolete project. You should set its
to “Unsupported” and its to “Obsolete”.After completing the form to create a sandbox project, you must press
to have the sandbox created, you should get something like the screenshot below that confirms that the sandbox has been created.Press the
tab to learn the numeric short name of your project.You can no go ahead and clone the old project to your local system, followed by a git push of the complete repository to the newly created sandbox. Provided the short name of the old project is “oldproject”, your git user name is “gitusername”, and the short name of the newly created sandbox is “1234”, the following four commands in the Gnu/Linux CLI should do it:
$ git clone http://git.drupalcode.org/project/oldproject.git oldproject $ cd oldproject $ git remote set-url origin gitusername@git.drupalcode.org:sandbox/gitusername/1234.git $ git push --all origin
It is now possible to delete “oldproject” on Drupal.org. Deleting a full project will free its namespace so that it may be repurposed. The --all
argument to push ensures that commit history is preserved, and that all branches and tags of the old project is pushed.
Troubleshooting
If you are unable to push to a repo at Drupal.org and the error
message tells you that you “cannot access” or are “unable to access”
the repo, and this happens to be a project you have cloned previously
without having to authenticate, the most likely case is that the
remote origin url in your .git/config
file is not using
set up to use authentication.
This format uses authentication:
[remote "origin"] bob@git.drupalcode.org:project/jimsproject.git
This format does not use authentication:
[remote "origin"] https://git.drupal.org/project/jimsproject.git
The simplest fix is to clone the repo again, this time using authentication:
$ git clone --branch 1.0.x bob@git.drupal.org:project/jimsproject.git
Then change the new clone with your changes, commit, and push again.
Final word
In addition to downloading and applying patches, git will help you manage local source code and allow you do commit changes to the repository. But you commit to your local branch (not the one on the server). However, to get your changes back into the main branch, you do a git push. This transfers your committed changes up to the server.
Last update: 2019-07-23 [gh].