Git
The Eclipse Foundation reported in its annual community
survey that as of May 2014, Git is now the most widely used source code management tool, with 42.9% of professional software developers
reporting that they use Git as their primary source control system compared with 36.3% in 2013, 32% in 2012; or for Git responses excluding
use of GitHub: 33.3% in 2014, 30.3% in 2013, 27.6% in 2012 and 12.8% in 2011.
Git's primitives are not inherently a
source code management (SCM) system. They are
like more like a virtual filesystem. That might help to understand the design decisions you encounter in git. As Torvalds
explains,
In many ways you can just see git as a filesystem – it's
content-addressable, and it has a notion
of versioning, but I really really designed it coming at the problem from the viewpoint of a filesystem person (hey, kernels
is what I do), and I actually have absolutely zero interest in creating a traditional SCM system.
You might already have Git1 on your system because somebody installed it, To check this use which command:
which git
On RHEL and derivatives you can also use rpm command
rpm -q git
If you need to install it you can do it from main RHEL reposivry via yum install git
Consider also installing the software package named git-all. This package includes some additional dependency packages that add
more power to Git.
Basic GIT-related terms:
- Branch— Another line of development of a software product in the repository.
- Check out— To request a copy of a file so you can work on it; a typical feature of centralized version control
systems.
- Clone— To make a copy of a repository that exists somewhere locally (in another directory) or remotely (on another
server, or Git hosting site such as GitHub).
- Commit— A change that’s saved to a repository, recording itself into the timeline.
- Distributed— A characteristic of a system such that its operations can be performed without the need of a server
(as opposed to centralized).
- Repository— A storage area to which the file is added after comment command. This storage area is usually a directory
or folder with special operations for viewing the timeline of changes. The repository now has your file, and your working directory
is in a clean state. To confirm this, you’ll use the git status, git log, and git ls-files commands.
- Staging area— Area in which file is stored after add command
- Timeline— A set of events ordered by time, from the earliest to the most recent event; also known as a history.
- Version control— The practice of keeping track of changes such that you can always go back to a known state.
Initializing a Repository in an Existing Directory
You can create a Git project using two main approaches. The first takes an existing project or directory and imports it into Git.
The second clones an existing Git repository from another server.
If you’re starting to track an existing project in Git, you need to go to the project’s directory and type
git init
This creates a new subdirectory named .git
that contains all of your necessary repository files – a Git repository skeleton.
At this point, nothing in your project is tracked yet.
If you want to start version-controlling existing files (as opposed to an empty directory), you should probably begin tracking those
files and do an initial commit. You can accomplish that with a few git add
commands that specify the files you want to
track, followed by a git commit
:
$
git add *.py
$
git add LICENSE
$
git commit -m 'initial project version'
We’ll go over what these commands do in just a minute. At this point, you have a Git repository with tracked files and an initial
commit.
Cloning an Existing Repository
The copying of the existing repository to the local server is called cloning. Cloning performs three functions:
- Creates a local repository of the project under the project_name/.git directory in your home directory. The files of
the project in this location are considered to be “checked out” from the central repository.
- Creates a directory where you can directly see the files. This is called the working area. Changes made in the
working area are not immediately version controlled.
- Creates a staging area. The staging area is designed to store changes to files before you commit them to the local
repository.
If you want to get a copy of an existing Git repository – for example, a project you’d like to contribute to – the command you need
is git clone
. If you’re familiar with other VCS systems such as Subversion, you’ll notice that the command is “clone” and
not “checkout”. This is an important distinction – instead of getting just a working copy, Git receives a full copy of nearly all data
that the server has. Every version of every file for the history of the project is pulled down by default when you run git clone
.
In fact, if your server disk gets corrupted, you can often use nearly any of the clones on any client to set the server back to the
state it was in when it was cloned (you may lose some server-side hooks and such, but all the versioned data would be there
You clone a repository with git clone [url]
.
For example, if you want to clone the Git project named superproject, you can do so like this:
$
git clone https://github.com/superproject
That creates a directory named “superproject” in your home directory, initializes a .git
directory inside it,
downloads the data for that repository,
and checks out a working copy of the latest version. If you go into the new libgit2
directory, you’ll see the project files
in there, ready to be worked on or used. If you want to clone the repository into a directory named something other than “libgit2”,
you can specify that as the next command-line option:
$ git clone https://github.com/libgit2/superproject myproject
That command does the same thing as the previous one, but the target directory is called myproject
.
Git has a number of different transfer protocols you can use. The most common is the https://
protocol, but you
may also see git://
or user@server:path/to/repo.git
, which uses the SSH transfer protocol.
To see the location of your remote repository, execute the git remote -v command:
$ git remote -v
Getting Help
If you ever need help while using Git, there are three ways to get the manual page (manpage) help for any of the Git commands:
$
git help
<verb>;
$
git <
verb>
--help
$
man git-<
verb>;
For example, you can get the manpage help for the config command by running
$
git help
config
These commands are nice because you can access them anywhere, even offline. If the manpages and this book aren’t enough and you need
in-person help, you can try the #git
or #github
channel on the Freenode IRC server (irc.freenode.net). These
channels are regularly filled with hundreds of people who are all very knowledgeable about Git and are often willing to help.
Checking Your Settings
If you want to check your settings, you can use the git config --list
command to list all the settings Git can find
at that point:
$
git config --list
...
You may see keys more than once, because Git reads the same key from different files (/etc/gitconfig
and ~/.gitconfig
,
for example). In this case, Git uses the last value for each unique key it sees.
You can also check what Git thinks a specific key’s value is by typing git config <key>
:
$
git config user.name
Imagine you are working on some files one day and it’s
getting late. It is Friday afternoon and you just can’t wait for the weekend to
start. On the following Monday you arrive at work and realize you have no idea in
what area you left your file. Were they added to the staging area? All of them or
just some? Did you commit any of them to the local repository?
This is when you want to run the git status
command:
$ git status
A few tips on how to customize your Git environment
Now that you have Git on your system, you’ll want to do a few things to customize your Git environment. You should have to do these
things only once on any given computer; they’ll stick around between upgrades. You can also change them at any time by running through
the commands again.
Git comes with a tool called git config
that lets you get and set configuration variables that control all aspects of
how Git looks and operates. These
variables can be stored in three different places:
/etc/gitconfig
file: Contains values for every user on the system and all their repositories. If you pass
the option --system
to git config
, it reads and writes from this file specifically.
~/.gitconfig
or ~/.config/git/config
file: Specific to your user. You can make Git read and write
to this file specifically by passing the --global
option.
config
file in the Git directory (that is, .git/config
) of whatever repository you’re currently using:
Specific to that single repository.
Each level overrides values in the previous level, so values in .git/config
trump those in /etc/gitconfig
.
On Windows systems, Git looks for the .gitconfig
file in the $HOME
directory (C:\Users\$USER
for most people). It also still looks for /etc/gitconfig
, although it’s relative to the root, which is wherever you decide
to install Git on your Windows system when you run the installer. If you are using Git for Windows 2.x or later, there is also a system-level
config file at C:\Documents and Settings\All Users\Application Data\Git\config
on Windows XP, and in C:\ProgramData\Git\config
on Windows Vista and newer. This config file can only be changed by git config -f <file>
as an admin.
Your Identity
The first thing you should do when you install Git is to set your user name and email address. This is important because every Git
commit uses this information, and it’s immutably baked into the commits you start creating:
$
git config --global user.name "John Doe"
$
git config --global user.email [email protected]
Again, you need to do this only once if you pass the --global
option, because then Git will always use that information
for anything you do on that system. If you want to override this with a different name or email address for specific projects, you can
run the command without the --global
option when you’re in that project.
Many of the GUI tools will help you do this when you first run them.
Your Editor
Now that your identity is set up, you can configure the default text editor that will be used when Git needs you to type in a message.
If not configured, Git uses your system’s default editor, which is system dependant.
If you want to use a different text editor, such as VIM, you can do the following:
$
git config --global core.editor vim
While on a Windows system, if you want to use a different text editor, such as Notepad++, you can do the following:
On a x86 system
$
git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -nosession"
On a x64 system
$
git config --global core.editor "'C:/Program Files (x86)/Notepad++/notepad++.exe' -multiInst -nosession"
Warning: You may find, if you don’t setup an editor like this, you will likely get into a really confusing state when they are launched. Such
example on a Windows system may include a prematurely terminated Git operation during a Git initiated edit.
- 20201122 : How to present a GitHub project for your resume The HFT Guy ( Nov 22, 2020 , thehftguy.com )
- 20200714 : The life-changing magic of git rebase -i - Opensource.com ( Jul 14, 2020 , opensource.com )
- 20200712 : 6 handy Bash scripts for Git - Opensource.com ( Jul 12, 2020 , opensource.com )
- 20200116 : Linux Today - 6 Git mistakes you will make -- and how to fix them ( Jan 16, 2020 , www.linuxtoday.com )
- 20191108 : Five tips for using revision control in operations Opensource.com ( Nov 08, 2019 , opensource.com )
- 20191023 : Ignoring Files and Directories in Git (.gitignore) ( Oct 23, 2019 , linuxize.com )
- 20191007 : How to commit to remote git repository ( Apr 28, 2012 , stackoverflow.com )
- 20190822 : How to Remove Untracked Files in Git - Linuxize ( Aug 22, 2019 , linuxize.com )
- 20190726 : How To Create and List Local and Remote Git Branches ( Jul 22, 2019 , linuxize.com )
- 20190322 : Move your dotfiles to version control Opensource.com ( Mar 22, 2019 , opensource.com )
- 20180527 : A guide to Git branching by Kedar Vijay Kulkarni ( May 27, 2018 , opensource.com )
- 20180313 : git log - View the change history of a file using Git versioning ( Mar 13, 2018 , stackoverflow.com )
- 20180313 : GIT installation ( Mar 13, 2018 , www.amazon.com )
- 20180313 : GRV - A Tool for Viewing Git Repositories in Linux Terminal by Aaron Kili ( Mar 13, 2018 , www.tecmint.com )
- 20171031 : Committing part of a file by Tom Ryder ( Jan 22, 2012 , sanctum.geek.nz )
- 20171031 : Managing dot files with Git ( Oct 31, 2017 , sanctum.geek.nz )
- 20170716 : How to Install HTTP Git Server With Nginx on Ubuntu 16.04 by Hitesh Jethva ( Jul 13, 2017 , www.howtoforge.com )
- 20170320 : Git for Subversion users, Part 1 Getting started ( Mar 20, 2017 , www.ibm.com )
- 20170220 : Git - Branches Atlassian Git Tutorial ( Feb 20, 2017 , www.atlassian.com )
- 20170220 : Git Behind the Curtain What Happens When You Commit, Branch, and Merge - DZone DevOps ( Feb 20, 2017 , dzone.com )
- 20160913 : Saving changes Atlassian Git Tutorial ( Sep 13, 2016 , www.atlassian.com )
- 20160913 : How do I pull from a Git repository through an HTTP proxy ( Stack Overflow )
Whether the naming convention will be " doc " or " docs " is an unimportant
detail. For example, here are Simple Folder Structure
Conventions for GitHub projects:
- build # Compiled files (alternatively 'dist')^
- docs # Documentation files (alternatively 'doc')
- src # Source files (alternatively 'lib' or 'app
- test # Automated tests (alternatively 'spec' or
- tools # Tools and utilities^
- LICENSE
- README. md
The life-changing magic of git rebase -i Make everyone think you write perfect
code the first time (and make your patches easier to review and merge). 29 Apr 2020
Dave Neary (Red Hat) Feed 66
up 5 comments Image by : WOCinTech Chat. Modified by Opensource.com. CC BY-SA 4.0 x
Subscribe now
Get the highlights in your inbox every week.
https://opensource.com/eloqua-embedded-email-capture-block.html?offer_id=70160000000QzXNAA0
Software development is messy. So many wrong turns, typos to fix, quick hacks and kludges to
correct later, off-by-one errors you find late in the process. With version control, you have a
pristine record of every wrong turn and correction made during the process of creating the
"perfect" final product -- a patch ready to submit upstream. Like the outtakes from movies,
they are a little embarrassing and sometimes amusing.
Wouldn't it be great if you could use version control to save your work regularly at
waypoints, and then when you have something you are ready to submit for review, you could hide
all of that private drafting work and just submit a single, perfect patch? Meet git rebase -i ,
the perfect way to rewrite history and make everyone think that you produce perfect code the
first time!
What does git rebase do?
In case you're not familiar with the intricacies of Git, here is a brief overview. Under the
covers, Git associates different versions of your project with a unique identifier, which is
made up of a hash of the parent node's unique identifier, and the difference between the new
version and its parent node. This creates a tree of revisions, and each person who checks out
the project gets their own copy. Different people can take the project in different directions,
each starting from potentially different branch points.
master-private-branches.png
The master branch in the "origin" repo on the left and the private branch on your
personal copy on the right. Programming and development
There are two ways to integrate your work back with the master branch in the original
repository: one is to use git merge , and the other is to use git rebase . They work in very
different ways.
When you use git merge , a new commit is created on the master branch that includes all of
the changes from origin plus all of your local changes. If there are any conflicts (for
example, if someone else has changed a file you are also working with), these will be marked,
and you have an opportunity to resolve the conflicts before committing this merge commit to
your local repository. When you push your changes back to the parent repository, all of your
local work will appear as a branch for other users of the Git repository.
But git rebase works differently. It rewinds your commits and replays those commits again
from the tip of the master branch. This results in two main changes. First, since your commits
are now branching off a different parent node, their hashes will be recalculated, and anyone
who has cloned your repository may now have a broken copy of the repository. Second, you do not
have a merge commit, so any merge conflicts are identified as your changes are being replayed
onto the master branch, and you need to fix them before proceeding with the rebase. When you
push your changes now, your work does not appear on a branch, and it looks as though you wrote
all of your changes off the very latest commit to the master branch.
merge-commit-vs-rebase.png
Merge commits (left) preserve history, while rebase (right) rewrites history.
However, both of these options come with a downside: everyone can see all your scribbles and
edits as you worked through problems locally before you were ready to share your code. This is
where the --interactive (or -i for short) flag to git rebase comes into the
picture.
Introducing git rebase -i
The big advantage of git rebase is that it rewrites history. But why stop at just pretending
you branched off a later point? There is a way to go even further and rewrite how you arrived
at your ready-to-propose code: git rebase -i , an interactive git rebase .
This feature is the "magic time machine" function in Git. The flag allows you to make
sophisticated changes to revision history while doing a rebase. You can hide your mistakes!
Merge many small changes into one pristine feature patch! Reorder how things appear in revision
history!
git-rebase-i.png
When you run git rebase -i , you get an editor session listing all of the commits that are
being rebased and a number of options for what you can do to them. The default choice is pick
.
- Pick maintains the commit in your history.
- Reword allows you to change a commit message, perhaps to fix a typo or add additional
commentary.
- Edit allows you to make changes to the commit while in the process of replaying the
branch.
- Squash merges multiple commits into one.
- You can reorder commits by moving them around in the file.
When you are finished, simply save the final result, and the rebase will execute. At each
stage where you have chosen to modify a commit (either with reword , edit , squash , or when
there is a conflict), the rebase stops and allows you to make the appropriate changes before
continuing.
The example above results in "One-liner bug fix" and "Integrate new header everywhere" being
merged into one commit, and "New header for docs website" and "D'oh - typo. Fixed" into
another. Like magic, the work that went into the other commits is still there on your branch,
but the associated commits have disappeared from your history!
This makes it easy to submit a clean patch to an upstream project using git send-email or by
creating a pull request against the parent repository with your newly tidied up patchset. This
has a number of advantages, including that it makes your code easier to review, easier to
accept, and easier to merge.
Rodrigo Graça on 29 Apr 2020
😂 Im used to do this manually. I work, then stash my changes before commitiing
then checkout master, git pull, make new branch, apply my stash, commit and push.
Dave Neary on
07 May 2020
That works, but I find rebase -i works pretty well for balancing regular sync points and
clean PRs. Thanks for the feedback!
Knusper on 30 Apr 2020
I like that you used little post-it notes for your illustration 😁
Dave Neary on
07 May 2020
Thank you! I tried in Inkscape, but doodling on post-it notes ended up being faster
:-)
Joël
Krähemann on 12 May 2020
I like the history of my code and value to have one.
Further if you don't show history to master certain services might not recognized your
actual commits.
I prefer on master branch: `git merge -s recursive -X theirs 3.3.x`
6 handy Bash scripts for Git These six Bash scripts will make your life easier
when you're working with Git repositories. 15 Jan 2020 Bob Peterson (Red Hat) Feed 86
up 2 comments Image by : Opensource.com x Subscribe now
Get the highlights in your inbox every week.
https://opensource.com/eloqua-embedded-email-capture-block.html?offer_id=70160000000QzXNAA0
More on Git
I wrote a bunch of Bash scripts that make my life easier when I'm working with Git
repositories. Many of my colleagues say there's no need; that everything I need to do can be
done with Git commands. While that may be true, I find the scripts infinitely more convenient
than trying to figure out the appropriate Git command to do what I want. 1. gitlog
gitlog prints an abbreviated list of current patches against the master version. It prints
them from oldest to newest and shows the author and description, with H for HEAD , ^ for HEAD^
, 2 for HEAD~2, and so forth. For example:
$ gitlog
-----------------------[ recovery25 ]-----------------------
(snip)
11 340d27a33895 Bob Peterson gfs2: drain the ail2 list after io errors
10 9b3c4e6efb10 Bob Peterson gfs2: clean up iopen glock mess in gfs2_create_inode
9 d2e8c22be39b Bob Peterson gfs2: Do proper error checking for go_sync family of glops
8 9563e31f8bfd Christoph Hellwig gfs2: use page_offset in gfs2_page_mkwrite
7 ebac7a38036c Christoph Hellwig gfs2: don't use buffer_heads in gfs2_allocate_page_backing
6 f703a3c27874 Andreas Gruenbacher gfs2: Improve mmap write vs. punch_hole consistency
5 a3e86d2ef30e Andreas Gruenbacher gfs2: Multi-block allocations in gfs2_page_mkwrite
4 da3c604755b0 Andreas Gruenbacher gfs2: Fix end-of-file handling in gfs2_page_mkwrite
3 4525c2f5b46f Bob Peterson Rafael Aquini's slab instrumentation
2 a06a5b7dea02 Bob Peterson GFS2: Add go_get_holdtime to gl_ops
^ 8ba93c796d5c Bob Peterson gfs2: introduce new function remaining_hold_time and use it in
dq
H e8b5ff851bb9 Bob Peterson gfs2: Allow rgrps to have a minimum hold time
If I want to see what patches are on a different branch, I can specify an alternate
branch:
$ gitlog recovery24
2. gitlog.id
gitlog.id just prints the patch SHA1 IDs:
$ gitlog.id
-----------------------[ recovery25 ]-----------------------
56908eeb6940 2ca4a6b628a1 fc64ad5d99fe 02031a00a251 f6f38da7dd18 d8546e8f0023 fc3cc1f98f6b
12c3e0cb3523 76cce178b134 6fc1dce3ab9c 1b681ab074ca 26fed8de719b 802ff51a5670 49f67a512d8c
f04f20193bbb 5f6afe809d23 2030521dc70e dada79b3be94 9b19a1e08161 78a035041d3e f03da011cae2
0d2b2e068fcd 2449976aa133 57dfb5e12ccd 53abedfdcf72 6fbdda3474b3 49544a547188 187032f7a63c
6f75dae23d93 95fc2a261b00 ebfb14ded191 f653ee9e414a 0e2911cb8111 73968b76e2e3 8a3e4cb5e92c
a5f2da803b5b 7c9ef68388ed 71ca19d0cba8 340d27a33895 9b3c4e6efb10 d2e8c22be39b 9563e31f8bfd
ebac7a38036c f703a3c27874 a3e86d2ef30e da3c604755b0 4525c2f5b46f a06a5b7dea02 8ba93c796d5c
e8b5ff851bb9
Again, it assumes the current branch, but I can specify a different branch if I
want.
3. gitlog.id2
gitlog.id2 is the same as gitlog.id but without the branch line at the top. This is handy
for cherry-picking all patches from one branch to the current branch:
$ # create a new
branch
$ git branch --track origin/master
$ # check out the new branch I just created
$ git checkout recovery26
$ # cherry-pick all patches from the old branch to the new one
$ for i in `gitlog.id2 recovery25` ; do git cherry-pick $i ;done 4. gitlog.grep
gitlog.grep greps for a string within that collection of patches. For example, if I find a
bug and want to fix the patch that has a reference to function inode_go_sync , I simply
do:
$ gitlog.grep inode_go_sync
-----------------------[ recovery25 - 50 patches ]-----------------------
(snip)
11 340d27a33895 Bob Peterson gfs2: drain the ail2 list after io errors
10 9b3c4e6efb10 Bob Peterson gfs2: clean up iopen glock mess in gfs2_create_inode
9 d2e8c22be39b Bob Peterson gfs2: Do proper error checking for go_sync family of glops
152:-static void inode_go_sync(struct gfs2_glock *gl)
153:+static int inode_go_sync(struct gfs2_glock *gl)
163:@@ -296,6 +302,7 @@ static void inode_go_sync(struct gfs2_glock *gl)
8 9563e31f8bfd Christoph Hellwig gfs2: use page_offset in gfs2_page_mkwrite
7 ebac7a38036c Christoph Hellwig gfs2: don't use buffer_heads in gfs2_allocate_page_backing
6 f703a3c27874 Andreas Gruenbacher gfs2: Improve mmap write vs. punch_hole consistency
5 a3e86d2ef30e Andreas Gruenbacher gfs2: Multi-block allocations in gfs2_page_mkwrite
4 da3c604755b0 Andreas Gruenbacher gfs2: Fix end-of-file handling in gfs2_page_mkwrite
3 4525c2f5b46f Bob Peterson Rafael Aquini's slab instrumentation
2 a06a5b7dea02 Bob Peterson GFS2: Add go_get_holdtime to gl_ops
^ 8ba93c796d5c Bob Peterson gfs2: introduce new function remaining_hold_time and use it in
dq
H e8b5ff851bb9 Bob Peterson gfs2: Allow rgrps to have a minimum hold time
So, now I know that patch HEAD~9 is the one that needs fixing. I use git rebase -i HEAD~10
to edit patch 9, git commit -a --amend , then git rebase --continue to make the necessary
adjustments.
5. gitbranchcmp3
gitbranchcmp3 lets me compare my current branch to another branch, so I can compare older
versions of patches to my newer versions and quickly see what's changed and what hasn't. It
generates a compare script (that uses the KDE tool Kompare , which works on GNOME3,
as well) to compare the patches that aren't quite the same. If there are no differences other
than line numbers, it prints [SAME] . If there are only comment differences, it prints [same]
(in lower case). For example:
$ gitbranchcmp3 recovery24
Branch recovery24 has 47 patches
Branch recovery25 has 50 patches
(snip)
38 87eb6901607a 340d27a33895 [same] gfs2: drain the ail2 list after io errors
39 90fefb577a26 9b3c4e6efb10 [same] gfs2: clean up iopen glock mess in gfs2_create_inode
40 ba3ae06b8b0e d2e8c22be39b [same] gfs2: Do proper error checking for go_sync family of
glops
41 2ab662294329 9563e31f8bfd [SAME] gfs2: use page_offset in gfs2_page_mkwrite
42 0adc6d817b7a ebac7a38036c [SAME] gfs2: don't use buffer_heads in
gfs2_allocate_page_backing
43 55ef1f8d0be8 f703a3c27874 [SAME] gfs2: Improve mmap write vs. punch_hole consistency
44 de57c2f72570 a3e86d2ef30e [SAME] gfs2: Multi-block allocations in gfs2_page_mkwrite
45 7c5305fbd68a da3c604755b0 [SAME] gfs2: Fix end-of-file handling in gfs2_page_mkwrite
46 162524005151 4525c2f5b46f [SAME] Rafael Aquini's slab instrumentation
47 a06a5b7dea02 [ ] GFS2: Add go_get_holdtime to gl_ops
48 8ba93c796d5c [ ] gfs2: introduce new function remaining_hold_time and use it in dq
49 e8b5ff851bb9 [ ] gfs2: Allow rgrps to have a minimum hold time
Missing from recovery25:
The missing:
Compare script generated at: /tmp/compare_mismatches.sh 6. gitlog.find
Finally, I have gitlog.find , a script to help me identify where the upstream versions of my
patches are and each patch's current status. It does this by matching the patch description. It
also generates a compare script (again, using Kompare) to compare the current patch to the
upstream counterpart:
$ gitlog.find
-----------------------[ recovery25 - 50 patches ]-----------------------
(snip)
11 340d27a33895 Bob Peterson gfs2: drain the ail2 list after io errors
lo 5bcb9be74b2a Bob Peterson gfs2: drain the ail2 list after io errors
10 9b3c4e6efb10 Bob Peterson gfs2: clean up iopen glock mess in gfs2_create_inode
fn 2c47c1be51fb Bob Peterson gfs2: clean up iopen glock mess in gfs2_create_inode
9 d2e8c22be39b Bob Peterson gfs2: Do proper error checking for go_sync family of glops
lo feb7ea639472 Bob Peterson gfs2: Do proper error checking for go_sync family of glops
8 9563e31f8bfd Christoph Hellwig gfs2: use page_offset in gfs2_page_mkwrite
ms f3915f83e84c Christoph Hellwig gfs2: use page_offset in gfs2_page_mkwrite
7 ebac7a38036c Christoph Hellwig gfs2: don't use buffer_heads in gfs2_allocate_page_backing
ms 35af80aef99b Christoph Hellwig gfs2: don't use buffer_heads in
gfs2_allocate_page_backing
6 f703a3c27874 Andreas Gruenbacher gfs2: Improve mmap write vs. punch_hole consistency
fn 39c3a948ecf6 Andreas Gruenbacher gfs2: Improve mmap write vs. punch_hole consistency
5 a3e86d2ef30e Andreas Gruenbacher gfs2: Multi-block allocations in gfs2_page_mkwrite
fn f53056c43063 Andreas Gruenbacher gfs2: Multi-block allocations in gfs2_page_mkwrite
4 da3c604755b0 Andreas Gruenbacher gfs2: Fix end-of-file handling in gfs2_page_mkwrite
fn 184b4e60853d Andreas Gruenbacher gfs2: Fix end-of-file handling in gfs2_page_mkwrite
3 4525c2f5b46f Bob Peterson Rafael Aquini's slab instrumentation
Not found upstream
2 a06a5b7dea02 Bob Peterson GFS2: Add go_get_holdtime to gl_ops
Not found upstream
^ 8ba93c796d5c Bob Peterson gfs2: introduce new function remaining_hold_time and use it in
dq
Not found upstream
H e8b5ff851bb9 Bob Peterson gfs2: Allow rgrps to have a minimum hold time
Not found upstream
Compare script generated: /tmp/compare_upstream.sh
The patches are shown on two lines, the first of which is your current patch, followed by
the corresponding upstream patch, and a 2-character abbreviation to indicate its upstream
status:
- lo means the patch is in the local upstream Git repo only (i.e., not pushed upstream
yet).
- ms means the patch is in Linus Torvald's master branch.
- fn means the patch is pushed to my "for-next" development branch, intended for the next
upstream merge window.
Some of my scripts make assumptions based on how I normally work with Git. For example, when
searching for upstream patches, it uses my well-known Git tree's location. So, you will need to
adjust or improve them to suit your conditions. The gitlog.find script is designed to locate
GFS2 and DLM patches only, so unless
you're a GFS2 developer, you will want to customize it to the components that interest
you.
Source code
Here is the source for these scripts.
1. gitlog #!/bin/bash
branch = $1
if test "x $branch " = x; then
branch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
fi
patches = 0
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
LIST = ` git log --reverse --abbrev-commit --pretty =oneline $tracking .. $branch | cut -d '
' -f1 | paste -s -d ' ' `
for i in $LIST ; do patches =$ ( echo $patches + 1 | bc ) ; done
if [[ $branch =~ . * for-next. * ]]
then
start =HEAD
# start=origin/for-next
else
start =origin / master
fi
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
/ usr / bin / echo "-----------------------[" $branch "]-----------------------"
patches =$ ( echo $patches - 1 | bc ) ;
for i in $LIST ; do
if [ $patches -eq 1 ] ; then
cnt = " ^"
elif [ $patches -eq 0 ] ; then
cnt = " H"
else
if [ $patches -lt 10 ] ; then
cnt = " $patches "
else
cnt = " $patches "
fi
fi
/ usr / bin / git show --abbrev-commit -s --pretty =format: " $cnt %h %<|(32)%an %s %n"
$i
patches =$ ( echo $patches - 1 | bc )
done
#git log --reverse --abbrev-commit --pretty=format:"%h %<|(32)%an %s" $tracking..$branch
#git log --reverse --abbrev-commit --pretty=format:"%h %<|(32)%an %s" ^origin/master
^linux-gfs2/for-next $branch 2. gitlog.id #!/bin/bash
branch = $1
if test "x $branch " = x; then
branch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
fi
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
/ usr / bin / echo "-----------------------[" $branch "]-----------------------"
git log --reverse --abbrev-commit --pretty =oneline $tracking .. $branch | cut -d ' ' -f1 |
paste -s -d ' ' 3. gitlog.id2 #!/bin/bash
branch = $1
if test "x $branch " = x; then
branch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
fi
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
git log --reverse --abbrev-commit --pretty =oneline $tracking .. $branch | cut -d ' ' -f1 |
paste -s -d ' ' 4. gitlog.grep #!/bin/bash
param1 = $1
param2 = $2
if test "x $param2 " = x; then
branch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
string = $param1
else
branch = $param1
string = $param2
fi
patches = 0
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
LIST = ` git log --reverse --abbrev-commit --pretty =oneline $tracking .. $branch | cut -d '
' -f1 | paste -s -d ' ' `
for i in $LIST ; do patches =$ ( echo $patches + 1 | bc ) ; done
/ usr / bin / echo "-----------------------[" $branch "-" $patches "patches
]-----------------------"
patches =$ ( echo $patches - 1 | bc ) ;
for i in $LIST ; do
if [ $patches -eq 1 ] ; then
cnt = " ^"
elif [ $patches -eq 0 ] ; then
cnt = " H"
else
if [ $patches -lt 10 ] ; then
cnt = " $patches "
else
cnt = " $patches "
fi
fi
/ usr / bin / git show --abbrev-commit -s --pretty =format: " $cnt %h %<|(32)%an %s" $i
/ usr / bin / git show --pretty =email --patch-with-stat $i | grep -n " $string "
patches =$ ( echo $patches - 1 | bc )
done 5. gitbranchcmp3 #!/bin/bash
#
# gitbranchcmp3 <old branch> [<new_branch>]
#
oldbranch = $1
newbranch = $2
script = / tmp / compare_mismatches.sh
/ usr / bin / rm -f $script
echo "#!/bin/bash" > $script
/ usr / bin / chmod 755 $script
echo "# Generated by gitbranchcmp3.sh" >> $script
echo "# Run this script to compare the mismatched patches" >> $script
echo " " >> $script
echo "function compare_them()" >> $script
echo "{" >> $script
echo " git show --pretty=email --patch-with-stat \$ 1 > /tmp/gronk1" >> $script
echo " git show --pretty=email --patch-with-stat \$ 2 > /tmp/gronk2" >> $script
echo " kompare /tmp/gronk1 /tmp/gronk2" >> $script
echo "}" >> $script
echo " " >> $script
if test "x $newbranch " = x; then
newbranch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
fi
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
declare -a oldsha1s = ( ` git log --reverse --abbrev-commit --pretty =oneline $tracking ..
$oldbranch | cut -d ' ' -f1 | paste -s -d ' ' ` )
declare -a newsha1s = ( ` git log --reverse --abbrev-commit --pretty =oneline $tracking ..
$newbranch | cut -d ' ' -f1 | paste -s -d ' ' ` )
#echo "old: " $oldsha1s
oldcount = ${#oldsha1s[@]}
echo "Branch $oldbranch has $oldcount patches"
oldcount =$ ( echo $oldcount - 1 | bc )
#for o in `seq 0 ${#oldsha1s[@]}`; do
# echo -n ${oldsha1s[$o]} " "
# desc=`git show $i | head -5 | tail -1|cut -b5-`
#done
#echo "new: " $newsha1s
newcount = ${#newsha1s[@]}
echo "Branch $newbranch has $newcount patches"
newcount =$ ( echo $newcount - 1 | bc )
#for o in `seq 0 ${#newsha1s[@]}`; do
# echo -n ${newsha1s[$o]} " "
# desc=`git show $i | head -5 | tail -1|cut -b5-`
#done
echo
for new in ` seq 0 $newcount ` ; do
newsha = ${newsha1s[$new]}
newdesc = ` git show $newsha | head -5 | tail -1 | cut -b5- `
oldsha = " "
same = "[ ]"
for old in ` seq 0 $oldcount ` ; do
if test " ${oldsha1s[$old]} " = "match" ; then
continue ;
fi
olddesc = ` git show ${oldsha1s[$old]} | head -5 | tail -1 | cut -b5- `
if test " $olddesc " = " $newdesc " ; then
oldsha = ${oldsha1s[$old]}
#echo $oldsha
git show $oldsha | tail -n + 2 | grep -v "index.*\.\." | grep -v "@@" > / tmp / gronk1
git show $newsha | tail -n + 2 | grep -v "index.*\.\." | grep -v "@@" > / tmp / gronk2
diff / tmp / gronk1 / tmp / gronk2 &> / dev / null
if [ $? -eq 0 ] ; then
# No differences
same = "[SAME]"
oldsha1s [ $old ] = "match"
break
fi
git show $oldsha | sed -n '/diff/,$p' | grep -v "index.*\.\." | grep -v "@@" > / tmp /
gronk1
git show $newsha | sed -n '/diff/,$p' | grep -v "index.*\.\." | grep -v "@@" > / tmp /
gronk2
diff / tmp / gronk1 / tmp / gronk2 &> / dev / null
if [ $? -eq 0 ] ; then
# Differences in comments only
same = "[same]"
oldsha1s [ $old ] = "match"
break
fi
oldsha1s [ $old ] = "match"
echo "compare_them $oldsha $newsha " >> $script
fi
done
echo " $new $oldsha $newsha $same $newdesc "
done
echo
echo "Missing from $newbranch :"
the_missing = ""
# Now run through the olds we haven't matched up
for old in ` seq 0 $oldcount ` ; do
if test ${oldsha1s[$old]} ! = "match" ; then
olddesc = ` git show ${oldsha1s[$old]} | head -5 | tail -1 | cut -b5- `
echo " ${oldsha1s[$old]} $olddesc "
the_missing = ` echo " $the_missing ${oldsha1s[$old]} " `
fi
done
echo "The missing: " $the_missing
echo "Compare script generated at: $script "
#git log --reverse --abbrev-commit --pretty=oneline $tracking..$branch | cut -d ' ' -f1 |paste
-s -d ' ' 6. gitlog.find #!/bin/bash
#
# Find the upstream equivalent patch
#
# gitlog.find
#
cwd = $PWD
param1 = $1
ubranch = $2
patches = 0
script = / tmp / compare_upstream.sh
echo "#!/bin/bash" > $script
/ usr / bin / chmod 755 $script
echo "# Generated by gitbranchcmp3.sh" >> $script
echo "# Run this script to compare the mismatched patches" >> $script
echo " " >> $script
echo "function compare_them()" >> $script
echo "{" >> $script
echo " cwd= $PWD " >> $script
echo " git show --pretty=email --patch-with-stat \$ 2 > /tmp/gronk2" >> $script
echo " cd ~/linux.git/fs/gfs2" >> $script
echo " git show --pretty=email --patch-with-stat \$ 1 > /tmp/gronk1" >> $script
echo " cd $cwd " >> $script
echo " kompare /tmp/gronk1 /tmp/gronk2" >> $script
echo "}" >> $script
echo " " >> $script
#echo "Gathering upstream patch info. Please wait."
branch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
tracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
cd ~ / linux.git
if test "X ${ubranch} " = "X" ; then
ubranch = ` git branch -a | grep "*" | cut -d ' ' -f2 `
fi
utracking = ` git rev-parse --abbrev-ref --symbolic-full-name @ { u } `
#
# gather a list of gfs2 patches from master just in case we can't find it
#
#git log --abbrev-commit --pretty=format:" %h %<|(32)%an %s" master |grep -i -e "gfs2" -e
"dlm" > /tmp/gronk
git log --reverse --abbrev-commit --pretty =format: "ms %h %<|(32)%an %s" master fs / gfs2 /
> / tmp / gronk.gfs2
# ms = in Linus's master
git log --reverse --abbrev-commit --pretty =format: "ms %h %<|(32)%an %s" master fs / dlm /
> / tmp / gronk.dlm
cd $cwd
LIST = ` git log --reverse --abbrev-commit --pretty =oneline $tracking .. $branch | cut -d ' '
-f1 | paste -s -d ' ' `
for i in $LIST ; do patches =$ ( echo $patches + 1 | bc ) ; done
/ usr / bin / echo "-----------------------[" $branch "-" $patches "patches
]-----------------------"
patches =$ ( echo $patches - 1 | bc ) ;
for i in $LIST ; do
if [ $patches -eq 1 ] ; then
cnt = " ^"
elif [ $patches -eq 0 ] ; then
cnt = " H"
else
if [ $patches -lt 10 ] ; then
cnt = " $patches "
else
cnt = " $patches "
fi
fi
/ usr / bin / git show --abbrev-commit -s --pretty =format: " $cnt %h %<|(32)%an %s" $i
desc = `/ usr / bin / git show --abbrev-commit -s --pretty =format: "%s" $i `
cd ~ / linux.git
cmp = 1
up_eq = ` git log --reverse --abbrev-commit --pretty =format: "lo %h %<|(32)%an %s"
$utracking .. $ubranch | grep " $desc " `
# lo = in local for-next
if test "X $up_eq " = "X" ; then
up_eq = ` git log --reverse --abbrev-commit --pretty =format: "fn %h %<|(32)%an %s" master..
$utracking | grep " $desc " `
# fn = in for-next for next merge window
if test "X $up_eq " = "X" ; then
up_eq = ` grep " $desc " / tmp / gronk.gfs2 `
if test "X $up_eq " = "X" ; then
up_eq = ` grep " $desc " / tmp / gronk.dlm `
if test "X $up_eq " = "X" ; then
up_eq = " Not found upstream"
cmp = 0
fi
fi
fi
fi
echo " $up_eq "
if [ $cmp -eq 1 ] ; then
UP_SHA1 = ` echo $up_eq | cut -d ' ' -f2 `
echo "compare_them $UP_SHA1 $i " >> $script
fi
cd $cwd
patches =$ ( echo $patches - 1 | bc )
done
echo "Compare script generated: $script "
javascript:false
<img alt="dcsimg" width="1" height="1"
src="//www.qsstats.com/dcsq968da00000oiyhzhxx6wa_7q6r/njs.gif?dcsuri=/index.php/developer/6-git-mistakes-you-will-make-and-how-to-fix-them.html&WT.js=No&WT.tv=10.4.1&dcssip=www.linuxtoday.com&WT.qs_dlk=XiAF1JT0q-EV9a-UG8XO4gAAABA&"/>
6 Git mistakes you will make -- and how to fix them Jan 15, 2020, 15:00 (
0 Talkback[s] )
(Other stories by Serdar Yegulalp )
A big reason developers use a source control system like Git is to avoid disasters. If
you do something as simple as mistakenly delete a file, or you discover that the changes
you've made to a dozen files were all ill-advised, you can undo what you've done with
little hassle.
Some Git mistakes are more intimidating and difficult to reverse, even for experienced
Git users. But with a little care -- and provided you don't panic -- you can roll back
from some of the worst Git disasters known to programmers.
|
javascript:false
<img alt="dcsimg" width="1" height="1"
src="//www.qsstats.com/dcsq968da00000oiyhzhxx6wa_7q6r/njs.gif?dcsuri=/index.php/developer/6-git-mistakes-you-will-make-and-how-to-fix-them.html&WT.js=No&WT.tv=10.4.1&dcssip=www.linuxtoday.com&WT.qs_dlk=XiAF1JT0q-EV9a-UG8XO4gAAABA&"/>
Whether you're still using Subversion (SVN), or have moved to a distributed system
like Git , revision control has found its
place in modern operations infrastructures. If you listen to talks at conferences and see what
new companies are doing, it can be easy to assume that everyone is now using revision control,
and using it effectively. Unfortunately that's not the case. I routinely interact with
organizations who either don't track changes in their infrastructure at all, or are not doing
so in an effective manner.
If you're looking for a way to convince your boss to spend the time to set it up, or are
simply looking for some tips to improve how use it, the following are five tips for using
revision control in operations.
1. Use revision control
A long time ago in a galaxy far, far away, systems administrators would log into servers and
make changes to configuration files and restart services manually. Over time we've worked to
automate much of this, to the point that members of your operations team may not even have
login access to your servers. Everything may be centrally managed through your configuration
management system, or other tooling, to automatically manage services. Whatever you're using to
handle your configurations and expectations in your infrastructure, you should have a history
of changes made to it.
Having a history of changes allows you to:
- Debug problems by matching up changes committed to the repository and deployed to
outages, infrastructure behavior changes, and other problems. It's tempting for us to always
blame developers for problems, but let's be honest, there are rare times when it is our
fault.
- Understand why changes were made. A good commit message, which you should insist upon,
will not only explain what the change is doing, but why the change is being
made. This will help your future colleagues, and your future self, understand why certain
architecture changes were made. The decisions may have been sound ones at the time, and
continue to make sense, or they were based on criteria that are no longer applicable to your
organization. By tracking these reasons, you can use decisions from the past to make better
decisions today.
- Revert to a prior state. Whether a change to your infrastructure caused problems and you
need to do a major roll-back, or a file was simply deleted before a backup was run, having
production changes stored in revision control allows you to go back in time to a known state
to recover.
This first move can often be the hardest thing for an organization to do. You're moving from
static configurations, or configuration management files on a filesystem, into a revision
control system which changes the process, and often the speed at which changes can be made.
Your engineers need to know how to use revision control and get used to the idea that all
changes they put into production will be tracked by other people in the organization.
2.
Have a plan for what should be put in revision control
This has a few major components: make sure you have multiple repositories in your
infrastructure that are targeted at specific services; don't put auto-generated content or
binaries into revision control; and make sure you're working securely.
First, you typically want to split up different parts of your services into different
repositories. This allows fine-tuned control of who has access to commit to specific service
repositories. It also prevents a repository from getting too large, which can complicate the
life of your systems adminstrators who are trying to copy it onto their systems.
You may not believe that a repository can get very big, since it's just text files, but
you'll have a different perspective when you have been using a repository for five years, and
every copy includes every change ever made. Let me show you the system-config repository
for the OpenStack Infrastructure project. The first commit to this project was made in July
2011:
elizabeth@r2d2$:~$ time git clone https://git.openstack.org/openstack-infra/system-config
Cloning into 'system-config'...
remote: Counting objects: 79237, done.
remote: Compressing objects: 100% (37446/37446), done.
remote: Total 79237 (delta 50215), reused 64257 (delta 35955)
Receiving objects: 100% (79237/79237), 10.52 MiB | 2.78 MiB/s, done.
Resolving deltas: 100% (50215/50215), done.
Checking connectivity... done.
real 0m7.600s
user 0m3.344s
sys 0m0.680s
That's over 10M of data for a text-only repository over five years.
Again, yes, text-only. You typically want to avoid stuffing binaries into revision control.
You often don't get diffs on these and they just bloat your repository. Find a better way to
distribute your binaries. You also don't want your auto-generated files in revision control.
Put the configuration file that creates those auto-generated files into revision
control, and let your configuration management tooling do its work to auto-generate the
files.
Finally, split out all secret data into separate repositories. Organizations can get a
considerable amount of benefit from allowing all of their technical staff see their
repositories, but you don't necessarily want to expose every private SSH or SSL key to
everyone. You may also consider open sourcing some of your tooling some day, so making sure you
have no private data in your repository from the beginning will prevent a lot of headaches
later on.
3. Make it your canonical place for changes, and deploy from it
Your revision control system must be a central part of your infrastructure. You can't just
update it when you remember to, or add it to a checklist of things you should be doing when you
make a change in production. Any time someone makes a change, it must be tracked in revision
control. If it's not, file a bug and make sure that configuration file is added to revision
control in the future.
This also means you should deploy from the configuration tracked in your revision control
system. No one should be able to log into a server and make changes without it being put
through revision control, except in rare case of an actual emergency where you have a strict
process for getting back on track as soon as the emergency has concluded.
4. Use
pre-commit scripts
Humans are pretty forgetful, and we sysadmins are routinely distracted and work in a very
interrupt-driven environments. Help yourself out and provide your systems administrators with
some scripts to remind them what the change message should include.
These reminders may include:
- Did you explain the reason for your change in the commit message?
- Did you include a reference to the bug, ticket, issue, etc related to this change?
- Did you update the documentation to reflect this change?
- Did you write/update a test for this change? (see the bonus tip at the end of this
article)
As a bonus, this also documents what reviewers of your change should look for.
Wait, reviewers? That's #5.
5: Hook it all into a code review system
Now that you have a healthy revision control system set up, you have an excellent platform
for adding another great tool for systems administrators: code review. This should typically be
a peer-review system where your fellow systems administrators of all levels can review changes,
and your changes will meet certain agreed-upon criteria for merging. This allows a whole team
to take responsibility for a change, and not just the person who came up with it. I like to
joke that since changes on our team requires two people to approve, it becomes the fault of
three people when something goes wrong, not just one!
Starting out, you don't need to do anything fancy, maybe just have team members submit a
merge proposal or pull request and socially make sure someone other than the proposer is the
one to merge it. Eventually you can look into more sophisticated code review systems. Most code
review tools allow features like inline commenting and discussion-style commenting which
provide a non-confrontational way to suggest changes to your colleagues. It's also great for
remotely
distributed teams who may not be talking face to face about changes, or even awake at the
same time.
Bonus: Do tests on every commit!
You have everything in revision control, you have your fellow humans review it, why not add
some robots? Start with simple things: Computers are very good at making sure files are
alphabetized, humans are not. Computers are really good at making sure syntax is correct (the
right number of spaces/tabs?); checking for these things is a waste of time for your brilliant
systems engineers. Once you have simple tests in place, start adding unit tests, functional
tests, and integration testing so you are confident that your changes won't break in
production. This will also help you find where you haven't automated your production
infrastructure and solve those problems in your development environment first. Topics
Sysadmin About the
author Elizabeth K. Joseph - After spending a decade doing Linux systems administration,
today Elizabeth K. Joseph works as a developer advocate at IBM focused on IBM Z. As a systems
administrator, she worked for a small services provider in Philadelphia before joining HPE
where she worked for four years on the geographically distributed OpenStack Infrastructure
team. This team runs the fully open source infrastructure for OpenStack development and lead to
an interest in other open source projects that have opened up their... More about me
Recommended reading
What you probably didn't know about sudo
Build web apps to automate sysadmin tasks
A guide to human communication for sysadmins
Linux permissions 101
16 essentials for sysadmin superheroes
What does it mean to be a sysadmin hero? 4 Comments
Jenifer Soflous
on 25 Jul 2016 Permalink
Great. This article is very useful for my research. I thank you so much for the
Author.
Ben Cotton
on 25 Jul 2016 Permalink
When I worked on a team of 10-ish, we had our SVN server email all commits to the team.
This gave everyone a chance to see what changes were being made in real time, even if it was
to a part of the system they didn't normally touch. I personally found it really useful in
improving my own work. I'd love to see more articles expanding on each of your points here
(hint hint!)
Elizabeth K.
Joseph on 27 Jul 2016 Permalink
The email point is a good one! We have optional tuning of email via our code review
system, and it's also allowed me to improve my own work and keep a better handle on what's
going on with the team when I'm traveling (and thus not hovering over IRC).
Shawn H
Corey on 25 Jul 2016 Permalink
For anyone using revision control, not just admins.
1. Add a critic to checkin. A critic not only checks syntax but for common bugs.
2. Add a tidy to checkin. It reformats the code into The Style. That way, developers don't
have to waste time making conform to The Style.
Often, when working on a project that uses Git, you'll want to exclude specific files or
directories from being pushed to the remote repository.
The .gitignore
file specifies what untracked files Git should
ignore.
What Files Should be Ignored?
Ignored files are usually platform-specific files or automatically created files from the
build systems. Some common examples include:
- Runtime files such as log, lock, cache, or temporary files.
- Files with sensitive information, such as passwords or API keys.
- Compiled code, such as
.class
or .o
.
- Dependency directories, such as
/vendor
or /node_modules
.
- Build directories, such as
/public
, /out
, or
/dist
.
- System files like
.DS_Store
or Thumbs.db
- IDE or text
editor configuration files.
.gitignore
Patterns
A .gitignore
file is a plain text file in which each line contains a pattern
for files or directories to ignore.
.gitignore
uses globbing patterns to match
filenames with wildcard characters. If you have files or directories containing a wildcard
pattern, you can use a single backslash ( \
) to escape the
character.
Comments
Lines starting with a hash mark ( #
) are comments and are ignored. Empty lines
can be used to improve the readability of the file and to group related lines of
patterns.
Slash
The slash symbol ( /
) represents a directory separator. The slash at the
beginning of a pattern is relative to the directory where the .gitignore
resides.
If the pattern starts with a slash, it matches files and directories only in the repository
root.
If the pattern doesn't start with a slash, it matches files and directories in any directory
or subdirectory.
If the pattern ends with a slash, it matches only directories. When a directory is ignored,
all of its files and subdirectories are also ignored.
Literal File Names
The most straightforward pattern is a literal file name without any special characters.
Pattern |
Example matches |
/access.log |
access.log |
access.log |
access.log
logs/access.log
var/logs/access.log |
build/ |
build |
Wildcard Symbols
*
- The asterisk symbol matches zero or more characters.
Pattern |
Example matches |
*.log |
error.log
logs/debug.log
build/logs/error.log |
**
- Two adjacent asterisk symbols match any file or zero or more directories.
When followed by a slash ( /
), it matches only directories.
Pattern |
Example matches |
logs/** |
Matches anything inside the logs directory. |
**/build |
var/build
pub/build
build |
foo/**/bar |
foo/bar
foo/a/bar
foo/a/b/c/bar |
?
- The question mark matches any single character.
Pattern |
Example matches |
access?.log |
access0.log
access1.log
accessA.log |
foo?? |
fooab
foo23
foo0s |
Square brackets
[...]
- Matches any of the characters enclosed in the square brackets. When two
characters are separated by a hyphen -
it denotes a range of characters. The range
includes all characters that are between those two characters. The ranges can be alphabetic or
numeric.
If the first character following the [
is an exclamation mark ( !
), then the pattern matches any character except those from the specified set.
Pattern |
Example matches |
*.[oa] |
file.o
file.a |
*.[!oa] |
file.s
file.1
file.0 |
access.[0-2].log |
access.0.log
access.1.log
access.2.log |
file.[a-c].out |
file.a.out
file.b.out
file.c.out |
file.[a-cx-z].out |
file.a.out
file.b.out
file.c.out
file.x.out
file.y.out
file.z.out |
access.[!0-2].log |
access.3.log
access.4.log
access.Q.log |
Negating Patterns
A pattern that starts with an exclamation mark ( !
) negates (re-include) any
file that is ignored by the previous pattern. The exception to this rule is to re-include a
file if its parent directory is excluded.
Pattern |
Example matches |
*.log
!error.log |
error.log or logs/error.log will not be ignored |
.gitignore
Example
Below is an example of what your .gitignore
file could look like:
# Ignore the node_modules directory
node_modules/
# Ignore Logs
logs
*.log
# Ignore the build directory
/dist
# The file containing environment variables
.env
# Ignore IDE specific files
.idea/
.vscode/
*.sw*
Local .gitignore
A local .gitignore
file is usually placed in the repository's root directory.
However you can create multiple .gitignore
files in different subdirectories in
your repository. The patterns in the .gitignore
files are matched relative to the
directory where the file resides.
Patterns defined in the files that reside in lower-level directories (sub-directories) have
precedence over those in higher-level directories.
Local .gitignore
files are shared with other developers and should contain
patterns that are useful for all other users of the repository.
Personal Ignore Rules
Patterns that are specific to your local repository and should not be distributed to other
repositories, should be set in the .git/info/exclude
file.
For example, you can use this file to ignore generated files from your personal project
tools.
Global .gitignore
Git also allows you to create a global .gitignore
file, where you can define
ignore rules for every Git repository on your local system.
The file can be named anything you like and stored in any location. The most common place to
keep this file is the home directory. You'll have to manually create the file and configure Git to
use it.
For example, to set ~/.gitignore_global
as the global Git ignore file, you
would do the following:
- Create the file:
touch ~/.gitignore_global
- Add the file to the Git configuration:
git config --global core.excludesfile ~/.gitignore_global
- Open the file with your text editor and add your rules to it.
Global rules are particularly useful for ignoring particular files that you never want to
commit, such as files with sensitive information or compiled executables.
Ignoring a
Previously Committed Files
The files in your working copy can be either tracked or untracked.
To ignore a file that has been previously committed, you'll need to unstage and remove the
file from the index, and then add a rule for the file in .gitignore
:
git rm --cached filename
The --cached
option tells git not to delete the file from the working tree but
only to remove it from the index.
To recursively remove a directory, use the -r
option:
git rm --cached filename
If you want to remove the file from both the index and local filesystem, omit the
--cached
option.
When recursively deleting files, use the -n
option that will perform a "dry
run" and show you what files will be deleted:
git rm -r -n directory
Debugging .gitignore
File
Sometimes it can be challenging to determine why a specific file is being ignored,
especially when you're are using multiple .gitignore
files or complex patterns.
This is where the git
check-ignore
command with the -v
option, which tells git to display
details about the matching pattern, comes handy.
For example, to check why the www/yarn.lock
file is ignored you would run:
git check-ignore -v www/yarn.lock
The output shows the path to the gitignore
file, the number of the matching
line, and the actual pattern.
www/.gitignore:31:/yarn.lock www/yarn.lock
The command also accepts more than one filename as arguments, and the file doesn't have to
exist in your working tree.
Displaying All Ignored Files
The git status
command with the --ignored
option displays a list
of all ignored files:
git status --ignored
Conclusion
The .gitignore
file allows you to exclude files from being checked into the
repository. The file contains globbing patterns that describe which files and directories
should be ignored.
gitignore.io is an online service that
allows you to generate .gitignore
files for your operating system, programming
language, or IDE.
If you have any questions or feedback, feel free to leave a comment.
Ahmed
,Apr 28, 2012 at 14:32
I am new to git.
I have done a clone of remote repo as follows
git clone https://[email protected]/repo.git
then I did
git checkout master
made some changes and committed these changes to my local repository like below..
git add .
git commit -m "my changes"
Now I have to push these changes to the remote repository.
I am not sure what to do.
Would I do a merge of my repo to remote ?
what steps do I need to take ?
I have git bash and git gui
please advise,
thanks,
zafarkhaja ,Apr 28, 2012 at
14:39
All You have to do is git push origin master
, where origin
is the
default name (alias) of Your remote repository and master
is the remote branch
You want to push Your changes to.
You may also want to check these out:
- http://gitimmersion.com/
- http://progit.org/book/
Sergey
K. ,Apr 28, 2012 at 14:54
You just need to make sure you have the rights to push to the remote repository and do
git push origin master
or simply
git push
haziz ,Apr
28, 2012 at 21:30
git push
or
git push server_name master
should do the trick, after you have made a commit to your local repository.
Bryan
Oakley ,Apr 28, 2012 at 14:34
Have you tried git push ?
gitref.org has a nice section dealing with
remote repositories .
You can also get help from the command line using the --help
option. For
example:
% git push --help
GIT-PUSH(1) Git Manual GIT-PUSH(1)
NAME
git-push - Update remote refs along with associated objects
SYNOPSIS
git push [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
[<repository> [<refspec>...]]
...
The files in the Git working directory can be either tracked or untracked.
Tracked
files are the ones that have been added and committed and git knows about. Tracked files can be unmodified, modified, or staged.
All other files in the working directory are untracked and git is not aware of those files.
Sometimes your git working directory may get cluttered up with unnecessary files that are either auto-generated, leftover from
merges or created by mistake. In those situations, you can either add those files in the
.gitignore
or
remove them. If you want to keep your repository nice and clean the better option is to remove the unnecessary files.
This
article explains how to remove untracked files in Git.
Removing Untracked Files
The
command that allows you to remove untracked files is
git
clean
.
https://tpc.googlesyndication.com/safeframe/1-0-35/html/container.html
It is
always a good idea to backup your repository because once deleted, the files and changes made to them cannot be recovered.
Before running the actual command and removing untracked files and directories use the
-n
option
that will perform a "dry run" and show you what files and directories will be deleted:
git clean -d -n
Copy
The
output will look something like this:
Would remove content/test/
Would remove content/blog/post/example.md
Copy
If some
of the files listed above are important, you should either start tracking these files with
git
add <file>
or add them to your
.gitignore
.
Once
you are sure you want to go ahead and delete the untracked files and directories, type:
git clean -d -f
Copy
The
command will print all successfully deleted files and directories:
Removing content/test/
Removing content/blog/post/example.md
Copy
The
-d
option
tells git to remove untracked directories too. If you don't want to delete empty untracked directories, omit
-d
option.
The
-f
option
stands for force. If not used and the Git configuration variable
clean.requireForce
is
set to true, Git will not delete the files.
If you want to interactively delete the untracked files use the
-i
option:
git clean -d -i
Copy
The output will show the files and directories to be removed, and ask you what to do with those files:
Would remove the following items:
content/test/ content/blog/post/example.md
*** Commands ***
1: clean 2: filter by pattern 3: select by numbers
4: ask each 5: quit 6: help
Copy
Select one of the choices and hit
Enter
.
me title=
If you want to limit the clean operation to given directories, pass the paths to the directories to be checked for untracked
files as arguments to the command. For example, to check for files under the
src
directory
you would run:
git clean -d -n src
Copy
Removing Ignored Files
The
git
clean
command also allows removing ignored files and directories.
To remove the all ignored and untracked files use the
-x
option:
git clean -d -n -x
Copy
If you want to remove only the ignored files and directories use the
-X
option:
git clean -d -n -X
Copy
The command above will delete all files and directories listed in your
.gitignore
and
keep the untracked files.
Conclusion
In this tutorial, we have shown you how to delete untracked files and directories in Git. Remember to always dry run the command
before actually deleting files.
If you have feedback, leave a comment below.
List Git Branches
To list all local Git branches use the git branch
or git branch --list
command:
git branch
dev
feature-a
feature-b
hotfix
* master
The current branch is highlighted with an asterisk *
. In this example that is the master
branch.
In Git, local and remote branches are separate objects. If you want to list both local and remote branches pass the -a
option:
git branch -a
dev
feature-a
feature-b
hotfix
* master
remotes/origin/regression-test-a
remotes/origin/regression-test-b
The -r
option will list only the remote branches.
git branch -r
Create a Git Branch
Creating a new branch is nothing more than creating a pointer to a given commit.
To create a new local branch use the git branch
command followed by the name of the new branch. For example, to create
a new branch named cool-feature
you would type:
git branch cool-feature
The command will return no output. If the branch with the same name already exists you will see the following error message:
fatal: A branch named 'cool-feature' already exists.
To start working on the branch and adding commits to it you will need to select the branch using git checkout
:
git checkout cool-feature
The output will inform you that the branch is switched:
Switched to branch 'cool-feature'
Instead of creating the branch and then switching to it, you can do that in a single command. When used with the -b
option the git checkout
command will create the given branch.
git checkout -b cool-feature
Switched to branch 'cool-feature'
From here you can use the standard git add
and git commit
commands to new commits to the new branch.
To push the new branch on the remote repository use the git push
command followed by the
remote repo name and branch name:
git push remote-repo cool-feature
Conclusion
In this tutorial, we have shown you how to list and create local and remote Git branches. Branches are a reference to a snapshot
of your changes and have a short life cycle.
With the git branch
command, you can also
Rename and
Delete local and remote Git branches.
If you hit a problem or have feedback, leave a comment below.
Move your dotfiles to version control Back up or sync your custom configurations
across your systems by sharing dotfiles on GitLab or GitHub. 20 Mar 2019 Matthew Broberg (Red Hat) Feed 11
up 4
comments x Get the newsletter
Join the 85,000 open source advocates who receive our giveaway alerts and article
roundups.
https://opensource.com/eloqua-embedded-email-capture-block.html?offer_id=70160000000QzXNAA0
What a Shell Dotfile Can Do For
You , H. "Waldo" Grunenwald goes into excellent detail about the why and how of setting
up your dotfiles. Let's dig into the why and how of sharing them. What's a dotfile?
"Dotfiles" is a common term for all the configuration files we have floating around our
machines. These files usually start with a . at the beginning of the filename, like .gitconfig
, and operating systems often hide them by default. For example, when I use ls -a on MacOS, it
shows all the lovely dotfiles that would otherwise not be in the output.
dotfiles on
master
➜ ls
README.md Rakefile bin misc profiles zsh-custom
dotfiles on master
➜ ls -a
. .gitignore .oh-my-zsh README.md zsh-custom
.. .gitmodules .tmux Rakefile
.gemrc .global_ignore .vimrc bin
.git .gvimrc .zlogin misc
.gitconfig .maid .zshrc profiles
If I take a look at one, .gitconfig , which I use for Git configuration, I see a ton of
customization. I have account information, terminal color preferences, and tons of aliases that
make my command-line interface feel like mine. Here's a snippet from the [alias] block:
87 #
Show the diff between the latest commit and the current state
88 d = !"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat"
89
90 # `git di $number` shows the diff between the state `$number` revisions ago and the current
state
91 di = !"d() { git diff --patch-with-stat HEAD~$1; }; git diff-index --quiet HEAD -- || clear;
d"
92
93 # Pull in remote changes for the current repository and all its submodules
94 p = !"git pull; git submodule foreach git pull origin master"
95
96 # Checkout a pull request from origin (of a github repository)
97 pr = !"pr() { git fetch origin pull/$1/head:pr-$1; git checkout pr-$1; }; pr"
Since my .gitconfig has over 200 lines of customization, I have no interest in rewriting it
on every new computer or system I use, and either does anyone else. This is one reason sharing
dotfiles has become more and more popular, especially with the rise of the social coding site
GitHub. The canonical article advocating for sharing dotfiles is Zach Holman's Dotfiles Are
Meant to Be Forked from 2008. The premise is true to this day: I want to share them, with
myself, with those new to dotfiles, and with those who have taught me so much by sharing their
customizations.
Sharing dotfiles
Many of us have multiple systems or know hard drives are fickle enough that we want to back
up our carefully curated customizations. How do we keep these wonderful files in sync across
environments?
My favorite answer is distributed version control, preferably a service that will handle the
heavy lifting for me. I regularly use GitHub and continue to enjoy GitLab as I get more
experienced with it. Either one is a perfect place to share your information. To set yourself
up:
- Sign into your preferred Git-based service.
- Create a repository called "dotfiles." (Make it public! Sharing is caring.)
- Clone it to your local environment. *
- Copy your dotfiles into the folder.
- Symbolically link (symlink) them back to their target folder (most often $HOME ).
- Push them to the remote repository.
* You may need to set up your Git configuration commands to clone the repository. Both
GitHub and GitLab will prompt you with the commands to run.
gitlab-new-project.png
Step 4 above is the crux of this effort and can be a bit tricky. Whether you use a script or
do it by hand, the workflow is to symlink from your dotfiles folder to the dotfiles destination
so that any updates to your dotfiles are easily pushed to the remote repository. To do this for
my .gitconfig file, I would enter:
$ cd dotfiles /
$ ln -nfs .gitconfig $HOME / .gitconfig
The flags added to the symlinking command offer a few additional benefits:
- -s creates a symbolic link instead of a hard link
- -f continues with other symlinking when an error occurs (not needed here, but useful in
loops)
- -n avoids symlinking a symlink (same as -h for other versions of ln )
You can review the IEEE and Open Group specification of ln and
the version on MacOS
10.14.3 if you want to dig deeper into the available parameters. I had to look up these
flags since I pulled them from someone else's dotfiles.
You can also make updating simpler with a little additional code, like the Rakefile I
forked from Brad Parbs .
Alternatively, you can keep it incredibly simple, as Jeff Geerling does in his dotfiles . He symlinks files using
this Ansible
playbook . Keeping everything in sync at this point is easy: you can cron job or
occasionally git push from your dotfiles folder.
Quick aside: What not to share
Before we move on, it is worth noting what you should not add to a shared dotfile
repository -- even if it starts with a dot. Anything that is a security risk, like files in
your .ssh/ folder, is not a good choice to share using this method. Be sure to double-check
your configuration files before publishing them online and triple-check that no API tokens are
in your files.
Where should I start?
If Git is new to you, my article about the terminology and
a cheat sheet of
my most frequently used commands should help you get going.
There are other incredible resources to help you get started with dotfiles. Years ago, I
came across dotfiles.github.io and
continue to go back to it for a broader look at what people are doing. There is a lot of tribal
knowledge hidden in other people's dotfiles. Take the time to scroll through some and don't be
shy about adding them to your own.
I hope this will get you started on the joy of having consistent dotfiles across your
computers.
What's your favorite dotfile trick? Add a comment or tweet me @mbbroberg . Topics Git GitHub GitLab About the author
Matthew Broberg - Matt loves working with technology communities to develop products and
content that invite delightful engagement. He's a serial podcaster, best known for the
Geek Whisperers podcast , is on the
board of the Influence
Marketing Council , co-maintains the DevRel Collective , and often shares his thoughts on
Twitter and GitHub ... More
about me
James Phillips on 20 Mar 2019 Permalink
See also the rcm suite for managing dotfiles from a central location. This provides the
subdirectory from which you can put your dotfiles into revision control.
Web refs:
https://fedoramagazine.org/managing-dotfiles-rcm/
https://github.com/thoughtbot/rcm
Chris
Hermansen on 20 Mar 2019 Permalink
An interesting article, Matt, thanks! I was glad to see "what not to share".
While most of my dot files hold no secrets, as you note some do - .ssh, .gnupg,
.local/share among others... could be some others. Thinking about this, my dot files are kind
of like my sock drawer - plenty of serviceable socks there, not sure I would want to share
them! Anyway a neat idea.
Mark Pitman on 21 Mar 2019 Permalink
Instead of linking your dotfiles, give YADM a try: https://yadm.io
It wraps the git command and keeps the actual git repository in a subdirectory.
Tom Payne on 21 Mar 2019 Permalink
Check out https://github.com/twpayne/chezmoi . It allows you
to store secrets securely, too. Disclaimer: I'm the author of chezmoi.
In this third article on getting started with Git, learn how to add and delete Git
branches. 16 May 2018 Kedar Vijay Kulkarni (Red Hat) Feed 24
up 1
comment Get the newsletter
Join the 85,000 open source advocates who receive our giveaway alerts and article
roundups.
In my two previous articles in this series, we started using Git and learned how
to clone, modify, add,
and delete Git files. In this third installment, we'll explore Git branching and why and
how it is used.
Picture this tree as a Git repository. It has a lot of branches, long and short, stemming
from the trunk and stemming from other branches. Let's say the tree's trunk represents a master
branch of our repo. I will use master
in this article as an alias for "master
branch" -- i.e., the central or first branch of a repo. To simplify things, let's assume that
the master
is a tree trunk and the other branches start from it.
Why we need
branches in a Git repo
Programming and development
The main reasons for having branches are:
- If you are creating a new feature for your project, there's a reasonable chance that
adding it could break your working code. This would be very bad for active users of your
project. It's better to start with a prototype, which you would want to design roughly in a
different branch and see how it works, before you decide whether to add the feature to the
repo's
master
for others to use.
- Another, probably more important, reason is Git was made for collaboration. If everyone starts
programming on top of your repo's
master
branch, it will cause a lot of
confusion. Everyone has different knowledge and experience (in the programming language
and/or the project); some people may write faulty/buggy code or simply the kind of
code/feature you may not want in your project. Using branches allows you to verify
contributions and select which to add to the project. (This assumes you are the only owner of
the repo and want full control of what code is added to it. In real-life projects, there are
multiple owners with the rights to merge code in a repo.)
Adding a branch
Let's go back to the previous article in
this series and see what branching in our Demo directory looks like. If you
haven't yet done so, follow the instructions in that article to clone the repo from GitHub and
navigate to Demo . Run the following commands:
pwd
git branch
ls -la
The pwd
command (which stands for present working directory) reports which
directory you're in (so you can check that you're in Demo ), git branch
lists all the branches on your computer in the Demo repository, and ls
-la
lists all the files in the PWD. Now your terminal will look like this:
There's only one file, README.md
, on the branch master. (Kindly ignore the
other directories and files listed.)
Next, run the following commands:
git status
git checkout -b myBranch
git status
The first command, git status
reports you are currently on branch
master
, and (as you can see in the terminal screenshot below) it is up to date with
origin/master
, which means all the files you have on your local copy of the
branch master are also present on GitHub. There is no difference between the two copies. All
commits are identical on both the copies as well.
The next command, git checkout -b myBranch
, -b
tells Git to
create a new branch and name it myBranch
, and checkout
switches us
to the newly created branch. Enter the third line, git status
, to verify you are
on the new branch you just created.
As you can see below, git status
reports you are on branch
myBranch
and there is nothing to commit. This is because there is neither a new
file nor any modification in existing files.
If you want to see a visual representation of branches, run the command gitk
.
If the computer complains bash: gitk: command not found
, then install
gitk
. (See documentation for your operating system for the install
instructions.)
The image below reports what we've done in Demo : Your last commit was Delete
file.txt
and there were three commits before that. The current commit is noted with a
yellow dot, previous commits with blue dots, and the three boxes between the yellow dot and
Delete file.txt
tell you where each branch is (i.e., what is the last commit on
each branch). Since you just created myBranch
, it is on the same commit as
master
and the remote counterpart of master
, namely
remotes/origin/master
. (A big thanks to Peter Savage from Red Hat who made me aware of
gitk
.)
Now let's create a new file on our branch myBranch
and let's observe terminal
output. Run the following commands:
echo "Creating a newFile on myBranch" > newFile
cat newFile
git status
The first command, echo
, creates a file named newFile
, and
cat newFile
shows what is written in it. git status
tells you the
current status of our branch myBranch
. In the terminal screenshot below, Git
reports there is a file called newFile
on myBranch
and
newFile
is currently untracked
. That means Git has not been told to
track any changes that happen to newFile
.
The next step is to add, commit, and push newFile
to myBranch
(go
back to the last article in this series for more details).
git add newFile
git commit -m "Adding newFile to myBranch"
git push origin myBranch
In these commands, the branch in the push
command is myBranch
instead of master
. Git is taking newFile
, pushing it to your
Demo repository in GitHub, and telling you it's created a new branch on GitHub that is
identical to your local copy of myBranch
. The terminal screenshot below details
the run of commands and its output.
If you go to GitHub, you can see there are two branches to pick from in the branch
drop-down.
Richard
,Nov 10, 2008 at 15:42
How can I view the change history of an individual file in Git, complete details with what
has changed?
I have got as far as:
git log -- [filename]
which shows me the commit history of the file, but how do I get at the content of each of
the file changes?
I'm trying to make the transition from MS SourceSafe and that used to be a simple
right-click
→ show history
.
chris ,May
10, 2010 at 8:58
The above link is no-longer valid. This link is working today: Git Community Book – chris
May 10 '10 at 8:58
Claudio
Acciaresi ,Aug 24, 2009 at 12:05
For this I'd use:
gitk [filename]
or to follow filename past renames
gitk --follow [filename]
Egon
Willighagen ,Apr 6, 2010 at 15:50
But I rather even have a tool that combined the above with 'git blame' allowing me to browse
the source of a file as it changes in time... – Egon Willighagen
Apr 6 '10 at 15:50
Dan
Moulding ,Mar 30, 2011 at 23:17
Unfortunately, this doesn't follow the history of the file past renames. – Dan Moulding
Mar 30 '11 at 23:17
Florian
Gutmann ,Apr 26, 2011 at 9:05
I was also looking for the history of files that were previously renamed and found this
thread first. The solution is to use "git log --follow <filename>" as Phil pointed out
here . – Florian Gutmann
Apr 26 '11 at 9:05
mikemaccana ,Jul 18, 2011 at
15:17
The author was looking for a command line tool. While gitk comes with GIT, it's neither a
command line app nor a particularly good GUI. – mikemaccana
Jul 18 '11 at 15:17
hdgarrood ,May 13, 2013 at
14:57
Was he looking for a command line tool? "right click -> show history" certainly doesn't
imply it. – hdgarrood
May 13 '13 at 14:57
VolkA ,Nov
10, 2008 at 15:56
You can use
git log -p filename
to let git generate the patches for each log entry.
See
git help log
for more options - it can actually do a lot of nice things :) To get just the diff for a
specific commit you can
git show HEAD
or any other revision by identifier. Or use
gitk
to browse the changes visually.
Jonas
Byström ,Feb 17, 2011 at 17:13
git show HEAD shows all files, do you know how to track an individual file (as Richard was
asking for)? – Jonas Byström
Feb 17 '11 at 17:13
Marcos
Oliveira ,Feb 9, 2012 at 21:44
you use: git show <revision> -- filename, that will show the diffs for that revision,
in case exists one. – Marcos Oliveira
Feb 9 '12 at 21:44
Raffi Khatchadourian ,May
9, 2012 at 22:29
--stat is also helpful. You can use it together with -p. – Raffi Khatchadourian
May 9 '12 at 22:29
Paulo
Casaretto ,Feb 27, 2013 at 18:05
This is great. gitk does not behave well when specifying paths that do not exist anymore. I
used git log -p -- path . – Paulo Casaretto
Feb 27 '13 at 18:05
ghayes
,Jul 21, 2013 at 19:28
Plus gitk looks like it was built by the boogie monster. This is a great answer and is best
tailored to the original question. – ghayes
Jul 21 '13 at 19:28
Dan
Moulding ,Mar 30, 2011 at 23:25
git log --follow -p -- file
This will show the entire history of the file (including history beyond renames and with
diffs for each change).
In other words, if the file named bar
was once named foo
, then
git log -p bar
(without the --follow
option) will only show the
file's history up to the point where it was renamed -- it won't show the file's history when
it was known as foo
. Using git log --follow -p bar
will show the
file's entire history, including any changes to the file when it was known as
foo
. The -p
option ensures that diffs are included for each
change.
Raffi Khatchadourian ,May
9, 2012 at 22:29
--stat is also helpful. You can use it together with -p. – Raffi Khatchadourian
May 9 '12 at 22:29
zzeroo
,Sep 6, 2012 at 14:11
Dan's answer is the only real one! git log --follow -p file
– zzeroo
Sep 6 '12 at 14:11
Trevor
Boyd Smith ,Sep 11, 2012 at 18:54
I agree this is the REAL answer. (1.) --follow
ensures that you see file renames
(2.) -p
ensures that you see how the file gets changed (3.) it is command line
only. – Trevor Boyd Smith
Sep 11 '12 at 18:54
Dan
Moulding ,May 28, 2015 at 16:10
@Benjohn The --
option tells Git that it has reached the end of the options and
that anything that follows --
should be treated as an argument. For git
log
this only makes any difference if you have a path name that begins with a
dash . Say you wanted to know the history of a file that has the unfortunate name
"--follow": git log --follow -p -- --follow
– Dan Moulding
May 28 '15 at 16:10
NHDaly
,May 30, 2015 at 6:03
@Benjohn: Normally, the --
is useful because it can also guard against any
revision
names that match the filename you've entered, which can actually be
scary. For example: If you had both a branch and a file named foo
, git
log -p foo
would show the git log history up to foo
, not the history for
the file foo
. But @DanMoulding is right that since the
--follow
command only takes a single filename as its argument, this is less
necessary since it can't be a revision
. I just learned that. Maybe you were
right to leave it out of your answer then; I'm not sure. – NHDaly
May 30 '15 at 6:03
Falken
,Jun 7, 2012 at 10:23
If you prefer to stay text-based, you may want to use tig .
Quick Install:
- apt-get :
# apt-get install tig
- Homebrew (OS X) :
$ brew install tig
Use it to view history on a single file: tig [filename]
Or browse detailed repo history: tig
Similar to gitk
but text based. Supports colors in terminal!
Tom
McKenzie ,Oct 24, 2012 at 5:28
Excellent text-based tool, great answer. I freaked out when I saw the dependencies for gitk
installing on my headless server. Would upvote again A+++ – Tom McKenzie
Oct 24 '12 at 5:28
gloriphobia ,Oct 27, 2017 at
12:05
You can look at specific files with tig too, i.e. tig -- path/to/specific/file
– gloriphobia
Oct 27 '17 at 12:05
farktronix ,Nov 11, 2008 at 6:12
git whatchanged
-p filename
is also equivalent to git log -p filename
in this case.
You can also see when a specific line of code inside a file was changed with git
blame filename
. This will print out a short commit id, the author, timestamp, and
complete line of code for every line in the file. This is very useful after you've found a
bug and you want to know when it was introduced (or who's fault it was).
rockXrock ,Mar 8, 2013 at 9:45
+1, but filename
is not optional in command git blame filename
.
– rockXrock
Mar 8 '13 at 9:45
ciastek
,Mar 18, 2014 at 8:03
"New users are encouraged to use git-log instead. (...) The command is kept primarily for
historical reasons;" – ciastek
Mar 18 '14 at 8:03
Mark
Fox ,Jul 30, 2013 at 18:55
SourceTree users
If you use SourceTree to visualize your repository (it's free and quite good) you can
right click a file and select Log Selected
The display (below) is much friendlier than gitk and most the other options listed.
Unfortunately (at this time) there is no easy way to launch this view from the command line
-- SourceTree's CLI currently just opens repos.
Chris ,Mar
13, 2015 at 13:07
I particularly like the option "Follow renamed files", which allows you to see if a file was
renamed or moved. – Chris
Mar 13 '15 at 13:07
Sam
Lewallen ,Jun 30, 2015 at 6:16
but unless i'm mistaken (please let me know!), one can only compare two versions at a time in
the gui? Are there any clients which have an elegant interface for diffing several different
versions at once? Possibly with a zoom-out view like in Sublime Text? That would be really
useful I think. – Sam Lewallen
Jun 30 '15 at 6:16
Mark
Fox ,Jun 30, 2015 at 18:47
@SamLewallen If I understand correctly you want to compare three different commits? This
sounds similar to a three-way merge (mine, yours, base) -- usually this strategy is used for
resolving merge conflicts not necessarily comparing three arbitrary commits. There are many
tools that support three way merges
stackoverflow.com/questions/10998728/ but the trick is feeding these tools the specific
revisions gitready.com/intermediate/2009/02/27/
– Mark
Fox
Jun 30 '15 at 18:47
Sam
Lewallen ,Jun 30, 2015 at 19:02
Thanks Mark Fox, that's what I mean. Do you happen to know of any applications that will do
that? – Sam Lewallen
Jun 30 '15 at 19:02
AechoLiu ,Jan 25 at 6:58
You save my life. You can use gitk
to find the SHA1
hash, and then
open SourceTree
to enter Log Selected..
based on the found
SHA1
. – AechoLiu
Jan 25 at 6:58
yllohy
,Aug 11, 2010 at 13:01
To show what revision and author last modified each line of a file:
git blame filename
or if you want to use the powerful blame GUI:
git gui blame filename
John Lawrence Aspden ,Dec
5, 2012 at 18:38
Summary of other answers after reading through them and playing a bit:
The usual command line command would be
git log --follow --all -p dir/file.c
But you can also use either gitk (gui) or tig (text-ui) to give much more human-readable
ways of looking at it.
gitk --follow --all -p dir/file.c
tig --follow --all -p dir/file.c
Under debian/ubuntu, the install command for these lovely tools is as expected :
sudo apt-get install gitk tig
And I'm currently using:
alias gdf='gitk --follow --all -p'
so that I can just type gdf dir
to get a focussed history of everything in
subdirectory dir
.
PopcornKing ,Feb 25, 2013 at
17:11
I think this is a great answer. Maybe you arent getting voted as well because you answer
other ways (IMHO better) to see the changes i.e. via gitk and tig in addition to git. –
PopcornKing
Feb 25 '13 at 17:11
parasrish ,Aug 16, 2016 at
10:04
Just to add to answer. Locate the path (in git space, up to which exists in repository
still). Then use the command stated above "git log --follow --all -p
<folder_path/file_path>". There may be the case, that the filde/folder would have been
removed over the history, hence locate the maximum path that exists still, and try to fetch
its history. works ! – parasrish
Aug 16 '16 at 10:04
cregox
,Mar 18, 2017 at 9:44
--all
is for all branches, the rest is explained in @Dan's answer –
cregox
Mar 18 '17 at 9:44
Palesz
,Jun 26, 2013 at 20:12
Add this alias to your .gitconfig:
[alias]
lg = log --all --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'\n--abbrev-commit --date=relative
And use the command like this:
> git lg
> git lg -- filename
The output will look almost exactly the same as the gitk output. Enjoy.
jmbeck
,Jul 22, 2013 at 14:40
After I ran that lg shortcut, I said (and I quote) "Beautiful!". However, note that the "\n"
after "--graph" is an error. – jmbeck
Jul 22 '13 at 14:40
Egel ,Mar
27, 2015 at 12:11
Also can be used git lg -p filename
- it returns a beautiful diff of searched
file. – Egel
Mar 27 '15 at 12:11
Jian ,Nov
19, 2012 at 6:25
I wrote git-playback for
this exact purpose
pip install git-playback
git playback [filename]
This has the benefit of both displaying the results in the command line (like git
log -p
) while also letting you step through each commit using the arrow keys (like
gitk
).
George
Anderson ,Sep 17, 2010 at 16:50
Or:
gitx -- <path/to/filename>
if you're using gitx
Igor
Ganapolsky ,Sep 4, 2011 at 16:19
For some reason my gitx opens up blank. – Igor Ganapolsky
Sep 4 '11 at 16:19
zdsbs
,Jan 3, 2014 at 18:17
@IgorGanapolsky you have to make sure you're at the root of your git repository –
zdsbs
Jan 3 '14 at 18:17
Adi
Shavit ,Aug 7, 2012 at 13:57
If you want to see the whole history of a file, including on all other
branches use:
gitk --all <filename>
lang2 ,Nov
10, 2015 at 11:53
Lately I discovered tig
and found it very useful. There are some cases I'd wish
it does A or B but most of the time it's rather neat.
For your case, tig <filename>
might be what you're looking for.
http://jonas.nitro.dk/tig/
PhiLho
,Nov 28, 2012 at 15:58
With the excellent Git
Extensions , you go to a point in the history where the file still existed (if it have
been deleted, otherwise just go to HEAD), switch to the File tree
tab,
right-click on the file and choose File history
.
By default, it follows the file through the renames, and the Blame
tab allows
to see the name at a given revision.
It has some minor gotchas, like showing fatal: Not a valid object name
in the
View
tab when clicking on the deletion revision, but I can live with that.
:-)
Evan
Hahn ,May 9, 2013 at 20:39
Worth noting that this is Windows-only. – Evan Hahn
May 9 '13 at 20:39
Shmil The
Cat ,Aug 9, 2015 at 17:42
@EvanHahn not accurate, via mono one can use GitExtension also on Linux, we use it on ubuntu
and quite happy w/ it. see git-extensions-documentation.readthedocs.org/en/latest/
– Shmil
The Cat
Aug 9 '15 at 17:42
cori ,Nov 10,
2008 at 15:56
If you're using the git GUI (on Windows) under the Repository menu you can use "Visualize
master's History". Highlight a commit in the top pane and a file in the lower right and
you'll see the diff for that commit in the lower left.
jmbeck
,Jul 22, 2013 at 14:42
How does this answer the question? – jmbeck
Jul 22 '13 at 14:42
cori ,Jul 22,
2013 at 15:34
Well, OP didn't specify command line, and moving from SourceSafe (which is a GUI) it seemed
relevant to point out that you could do pretty much the same thing that you can do in VSS in
the Git GUI on Windows. – cori
Jul 22 '13 at 15:34
Malks ,Dec
1, 2011 at 5:24
The answer I was looking for that wasn't in this thread is to see changes in files that I'd
staged for commit. i.e.
git diff --cached
ghayes
,Jul 21, 2013 at 19:47
If you want to include local (unstaged) changes, I often run git diff
origin/master
to show the complete differences between your local branch and the
master branch (which can be updated from remote via git fetch
) –
ghayes
Jul 21 '13 at 19:47
Brad
Koch ,Feb 22, 2014 at 0:04
-1, That's a diff, not a change history. – Brad Koch
Feb 22 '14 at 0:04
user3885927 ,Aug 12, 2015 at
21:22
If you use TortoiseGit you should be able to right click on the file and do TortoiseGit
--> Show Log
. In the window that pops up, make sure:
- '
Show Whole Project
' option is not checked.
- '
All Branches
' option is checked.
Noam
Manos ,Nov 30, 2015 at 10:54
TortoiseGit (and Eclipse Git as well) somehow misses revisions of the selected file, don't
count on it! – Noam Manos
Nov 30 '15 at 10:54
user3885927 ,Nov 30, 2015 at
23:20
@NoamManos, I haven't encountered that issue, so I cannot verify if your statement is
correct. – user3885927
Nov 30 '15 at 23:20
Noam
Manos ,Dec 1, 2015 at 12:06
My mistake, it only happens in Eclipse, but in TortoiseGit you can see all revisions of a
file if unchecking "show all project" + checking "all branches" (in case the file was
committed on another branch, before it was merged to main branch). I'll update your answer.
– Noam
Manos
Dec 1 '15 at 12:06
Lukasz
Czerwinski ,May 20, 2013 at 17:17
git diff -U <filename>
give you a unified diff.
It should be colored on red and green. If it's not, run: git config color.ui
auto
first.
jitendrapurohit ,Aug 13, 2015
at 5:41
You can also try this which lists the commits that has changed a specific part of a file
(Implemented in Git 1.8.4).
Result returned would be the list of commits that modified this particular part. Command
goes like :
git log --pretty=short -u -L <upperLimit>,<lowerLimit>:<path_to_filename>
where upperLimit is the start_line_number and lowerLimit is the ending_line_number of the
file.
Antonín
Slejška ,Jun 1, 2016 at 10:44
SmartGit :
- In the menu enable to display unchanged files: View / Show unchanged files
- Right click the file and select 'Log' or press 'Ctrl-L'
AhHatem
,Jan 2, 2013 at 19:35
If you are using eclipse with the git plugin, it has an excellent comparison view with
history. Right click the file and select "compare with"=> "history"
avgvstvs ,Sep 27, 2013 at 13:22
That won't allow you to find a deleted file however. – avgvstvs
Sep 27 '13 at 13:22
golimar
,Oct 30, 2012 at 14:35
Comparing two versions of a file is different to Viewing the change history of a file –
golimar
May 7 '15 at 10:22
You might already have Git 1 on your
system because it is sometimes installed by default (or another administrator might have
installed it). If you have access to the system as a regular user, you can execute the
following command to determine whether you have Git installed:
If Git is installed, then the path to the git
command is provided, as shown in
the preceding command. If it isn't installed, then you either get no output or an error like
the following:
[ocs@centos ~]# which git
/usr/bin/which: no git in
(/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin)
As an administrator on a Red Hat–based system, you could use the rpm
command to determine whether the git package has been installed:
[root@centos ~]# rpm -q git
git-1.8.3.1-6.el7_2.1.x86_64
If you are logged in as the root user on a Red Hat–based system, you can use the
following command to install Git:
yum install git
Consider installing the software package named git-all
. This package includes
some additional dependency packages that add more power to Git. Although you might not make use
of these features in this introductory book, having them available when you are ready to
perform more advanced Git functions will be good.
Mar 12, 2018
Requirements:
- Go version 1.5
or later should be installed on your system.
- libncursesw, libreadline and libcurl.
- cmake (to build libgit2).
Tecmint: GRV (Git Repository Viewer) is a free open-source and simple terminal-based
interface for viewing git repositories. It provides a way to view and search refs, commits,
branches and diffs using Vi/Vim like key bindings. Its behavior and style can be easily
customized through a configuration file.
Posted on One of
the advantages that Git has over Subversion and CVS is the use of its index as a staging
area , which turns out to be a much more flexible model than Subversion. One of the things
that always annoyed me about Subversion was that there seemed to be no elegant way to only
commit only some of your changes to a particular tracked file. Subversion deals only in files
in the working copy, and if you want to commit changes to a file, you have to commit
all the changes in that file, even if they're not related.
Where Subversion falls
short
As an example, suppose you're making changes to a working copy of a Subversion repository
called myproject
, and you've made a few changes to the main file,
myproject.php
; on one line, you've fixed a bug caused by getting the parameters
for htmlentities()
in the wrong order. On another, near the head of the file,
you've changed a php.ini
setting to allow the script to run for a long time.
Here's what the output of svn status
and svn diff
might look like in
this case:
$ svn status
M myproject.php
$ svn diff
Index: myproject.php
================================================================
--- myproject.php (revision 2)
+++ myproject.php (working copy)
@@ -1,5 +1,7 @@
<?php
+ini_set("max_execution_time", 300);
+
/**
* Open main class.
*/
@@ -120,7 +122,7 @@
public function dumpvalue($value)
{
- print htmlentities($value, "UTF-8", ENT_COMPAT);
+ print htmlentities($value, ENT_COMPAT, "UTF-8");
}
Under Subversion, unless you move files around, you can't commit only one of these changes;
you need to commit both. This isn't really the end of the world, since you could include a
commit message describing both things you changed:
$ svn commit -m "Allowed longer runtime, fixed parameter order bug"
Transmitting file data .
Committed revision 3.
But if you're finicky like me, and you'd prefer to think of commits as grouping semantically
related changes as much as possible, it would be much better to be able to commit these two
changes separately, and this is where Git's use of an index shines.
Git's method
Let's work with the same project again, but this time as a Git repository. We'll make the
same changes again, and view the output of git status
and git diff
:
$ git status
# On branch master
# Changes not staged for commit:
#
# modified: myproject.php
#
no changes added to commit
$ git diff
diff --git a/myproject.php b/myproject.php
index 7c20f21..c149190 100644
--- a/myproject.php
+++ b/myproject.php
@@ -1,5 +1,7 @@
<?php
+ini_set("max_execution_time", 300);
+
/**
* Open main class.
*/
@@ -120,7 +122,7 @@ class MyProject
public function dumpvalue($value)
{
- print htmlentities($value, "UTF-8", ENT_COMPAT);
+ print htmlentities($value, ENT_COMPAT, "UTF-8");
}
So far, so good. Now when we run git add myproject.php
to stage the changes in
the index ready for commit, by default it does the same thing Subversion does, putting
all of the changes in that file into the staging area. That's probably fine in most
cases, but today we want to commit one change, and then the other. The most basic way to do
this is using Git's --patch
option.
The --patch
option can be added to git add
, and to some other Git
commands concerned with manipulating the index as well, to explicitly prompt you about staging
or not staging different sections of the file, that it terms hunks . In our case, the
process of including only the first change would look something like this:
$ git add --patch myproject.php
diff --git a/myproject.php b/myproject.php
index 7c20f21..c149190 100644
--- a/myproject.php
+++ b/myproject.php
@@ -1,5 +1,7 @@
<?php
+ini_set("max_execution_time", 300);
+
/**
* Open main class.
*/
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -120,7 +122,7 @@ class MyProject
public function dumpvalue($value)
{
- print htmlentities($value, "UTF-8", ENT_COMPAT);
+ print htmlentities($value, ENT_COMPAT, "UTF-8");
}
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n
This done, if you compare the output of git diff --staged
and git
diff
, you'll notice that there are changes staged ready for commit in the file, and
also changes that are not staged that we can commit separately later:
$ git diff --staged
diff --git a/myproject.php b/myproject.php
index 7c20f21..4bb2362 100644
--- a/myproject.php
+++ b/myproject.php
@@ -1,5 +1,7 @@
<?php
+ini_set("max_execution_time", 300);
+
/**
* Open main class.
*/
$ git diff
diff --git a/myproject.php b/myproject.php
index 4bb2362..c149190 100644
--- a/myproject.php
+++ b/myproject.php
@@ -122,7 +122,7 @@ class MyProject
public function dumpvalue($value)
{
- print htmlentities($value, "UTF-8", ENT_COMPAT);
+ print htmlentities($value, ENT_COMPAT, "UTF-8");
}
So your staging area is all ready with just that one change in it, and all you need to do is
type git commit
with an appropriate message:
$ git commit -m "Allowed longer runtime"
[master 19d9068] Allowed longer runtime
1 files changed, 2 insertions(+), 0 deletions(-)
And the other change you made is still there, waiting to be staged and committed whenever
you see fit:
$ git diff
diff --git a/myproject.php b/myproject.php
index 4bb2362..c149190 100644
--- a/myproject.php
+++ b/myproject.php
@@ -122,7 +122,7 @@ class MyProject
public function dumpvalue($value)
{
- print htmlentities($value, "UTF-8", ENT_COMPAT);
+ print htmlentities($value, ENT_COMPAT, "UTF-8");
}
Other methods
Because Git's index can be manipulated with its lower-level tools very easily, you can treat
the differences between your changes and the index like any other diff
task. This
means more advanced tools like Fugitive for Vim can be even better for seeing
changesets in individual files as you stage them for commit. Check out Drew Neil's Vimcast series
on Fugitive if you're interested in doing this; it's quite an in-depth series of videos,
but very much worth watching if you're a Vim user who wants to understand and use Git to its
fullest, and you really value precision and clarity in your commits.
Posted on January 23, 2012 by
Tom Ryder Managing
configuration files in your home directory on a POSIX system can be a pain when you often work
on more than one machine, or when you accidentally remove or delete some useful option or file.
It turns out that it's beneficial to manage your configuration files via a version control
system, which will allow you both to track the changes you make, and also to easily implement
them on other machines. In this case, I'm going to show you how to do it with Git, but in
principle there's no reason most of this couldn't work with Subversion or Mercurial.
Choosing which files to version
A good way to start is to take a look at the dot files and dot directories you have storing
your carefully crafted configurations, and figure out for which of them it would be most
important to track changes and to be able to rapidly deploy on remote systems. I use the
following criteria:
- Compatibility: Is the configuration likely to work on all or most of the systems on which
you're going to use it? If you're going to check out your cutting edge
.vimrc
file on a remote Debian Sarge machine that hasn't been updated since 2006, you might find
that a lot of it doesn't work properly. In some cases, you can add conditionals to
configuration files so that they only load the option if it's actually available.
Similarly, you might not want to copy your .bashrc
to all of your machines if
you use a wide variety of them.
- Transferability: Are you going to want exactly the same behaviour this file configures on
all of your remote systems? If your
.gitconfig
file includes a personal handle
or outside e-mail address, it might not be appropriate for you to clone that onto your work
servers, since it'll end up in commits you do from work.
- Mutability: Are you going to be the only agent that updates this configuration, or will
programs change it as well, for example to store cached file references? This can make
updating a pain.
- Privacy: If you're going to put the file on GitHub or any other public repository
service, does it contain private information? You probably shouldn't put anything with API
keys, SSH keys, or database credentials out in the ether.
With these criteria applied, it turns out there are configurations for three programs that I
really want to be able to maintain easily across servers: my Vim configuration, my Git
configuration, and my GNU Screen configuration.
Creating the repository
To start, we'll create a directory called .dotfiles
to hold all our
configuration, and initialise it as an empty Git repository.
$ mkdir .dotfiles
$ cd .dotfiles
$ git init
Then we'll copy in the configuration files we want to track, and drop symbolic links to them
from where they used to be, so that the applications concerned read them correctly.
$ cd
$ mv .vim .dotfiles/vim
$ mv .vimrc .dotfiles/vimrc
$ mv .screenrc .dotfiles/screenrc
$ mv .gitconfig .dotfiles/gitconfig
$ ln -s .dotfiles/vim .vim
$ ln -s .dotfiles/vimrc .vimrc
$ ln -s .dotfiles/screenrc .screenrc
$ ln -s .dotfiles/gitconfig .gitconfig
Next, we drop into the .dotfiles
directory, add everything to the staging area,
and commit it:
$ cd .dotfiles
$ git add *
$ git commit -m "First commit of dotfiles."
And that's it, we've now got all four of those files tracked in our local Git
repository.
Using a remote repository
With that done, if you want to take the next step of having a central location where you can
always get your configuration from any machine with an internet connection, you can set up a
repository for your dot files on GitHub, with a free account. The instructions for doing this
on GitHub itself are great, so just follow them for your existing repository. On my machine,
the results look like this:
$ git remote add origin [email protected]:tejr/dotfiles.git
$ git push -u origin master
Note that I'm pushing using a public key setup, which you can arrange in the SSH Public Keys section of your GitHub
account settings.
With this done, if you update your configuration at any time, first add and commit the
changes to your local repository, and then all you need to do to update the GitHub version as
well is:
$ git push
Cloning onto another machine
Having done this, when you're working with a new machine onto which you'd like to clone your
configuration, you clone the repository from GitHub, and delete any existing versions of those
files in your home directory to replace them with symbolic links into your repository, like
so:
$ git clone [email protected]:tejr/dotfiles.git .dotfiles
$ rm -r .vim .vimrc .screenrc .gitconfig
$ ln -s .dotfiles/vim .vim
$ ln -s .dotfiles/vimrc .vimrc
$ ln -s .dotfiles/screenrc .screenrc
$ ln -s .dotfiles/gitconfig .gitconfig
Finally, if you come back to use this machine later after you've tweaked these configuration
files a bit and pushed them to GitHub, you can update them by just running a pull:
$ git pull
Making things easier
This ends up taking a lot of annoyances out of my day, as I know on any machine on which I
frequently work, all I need to do is drop to my .dotfiles
directory and run a
git pull
to get the most recent version of my configurations. This ends up being a
lot better than manually running scp
or rsync
calls to keep things up
to date. Posted in Git Tagged dot , dotfiles , files , github , versioning
Git is a free and open-source version control system that can be used to track changes of code.
Git allows you to create many repositories for the same application and coordinating work on those
files among multiple people. It is primarily used for source code management in software development.
In this article, we will learn how to install an HTTP Git Server With Nginx on Ubuntu 16.04.
Complete Story
Notable quotes:
"... The Wonderful Monkey Of Wittgenstein ..."
"... local ..."
"... branching ..."
"... automated regression test ..."
Getting started
Git gets demystified for Subversion version
control system users
Teodor Zlatanov
Published on August 04, 2009
Share this page
Facebook
Twitter
Linked
In
Google+
E-mail
this page
3
For anyone unfamiliar with free and open source version control
systems (VCSs), Subversion has become the standard non-commercial VCS,
replacing the old champ, Concurrent Versions System (CVS). CVS is still
just fine for limited use, but Subversion's allure is that it requires
only a little bit of setup on a Web server and not much beyond that.
Subversion does have some issues, which I'll discuss here, but for the
most part, it just works.
So, why do we need another one? Git (capital "G";
git
is
the command-line tool) is in many ways designed to be better than
Subversion. It is one of many
distributed
VCSs. My own first
experience with these was with Arch/tla, as well as Mercurial, Bazaar,
darcs, and a few others. For many reasons, which I'll discuss as far as
they are relevant, Git has become popular and is often considered
together with Subversion as the two leading choices for a personal or
corporate VCS.
There are two important reasons to be interested in Git if you are a
Subversion user.
- You are looking to move to Git because Subversion is limiting you
in some way.
- You are curious about Git and want to find out how it compares to
Subversion.
Well, perhaps there's a third reason: Git is a relatively hot
technology you want to include on your resume. I hope that's not your
primary goal; learning about Git is one of the most rewarding things a
developer can do. Even if you don't use Git now, the concepts and
workflow embodied in this distributed VCS are certain to be crucial
knowledge for most segments of the IT industry in the next 10 years as
the industry undergoes massive changes in scope and geographical
distribution.
Finally, though it might not be a compelling reason if you're not a
Linux kernel developer, the kernel and a number of other important
projects are maintained using Git, so you'll want to be familiar with it
if you plan to contribute.
This article is intended for beginning-to-intermediate Subversion
users. It requires beginner-level knowledge of Subversion and some
general knowledge of version control systems. The information here is
mainly for users of UNIX®-like (Linux® and Mac OS X) systems, with a
little bit thrown in for Windows® users.
Part 2 of this series will discuss more advanced uses of Git: merging
branches, generating diffs, and other common tasks.
Subversion and Git basics
Henceforth, I'll abbreviate "Subversion" as "SVN" to save wear and
tear on my U, B, E, R, S, I, and O keys.
git-svn
You may have heard of
git-svn
, a tool that lets you use
Git against a Subversion repository. Though useful in some situations,
being halfway distributed
and
using a centralized VCS is not
the same as switching to a distributed VCS.
So, what's SVN good for? You might already know this, but a VCS is not
about files; it's about changes. SVN, running on a central server, adds
changes to its repository of data and can give you a snapshot after every
change. That snapshot has a revision number; the revision number is very
important to SVN and the people who use it. If your change goes in after
mine, you're guaranteed to have a higher revision number.
Git has a similar goal-tracking changes-but has no centralized server.
This difference is crucial. Where SVN is centralized, Git is distributed;
therefore, Git has no way to provide an increasing revision number,
because there is no "latest revision." It still has unique revision IDs;
they are just not as useful on their own as the SVN revision numbers.
With Git, the crucial action is no longer the
commit
; it is
the
merge
. Anyone can clone a repository and commit to the
clone. The owner of the repository is given the choice of merging changes
back. Alternatively, developers can push changes back to the repository.
I'll explore only the latter, authorized-push model.
Keeping a directory under SVN
Let's start with a simple common example: tracking a directory's
contents with SVN. You'll need a SVN server and, obviously, a directory
of files, as well as an account on that server with commit rights for at
least one path. Get started by adding and committing the directory:
Listing 1. Setting up a directory under
SVN
1
2
3
4
5
|
% svn co
http://svnserver/...some path here.../top
% cd top
% cp -r
~/my_directory .
% svn add
my_directory
% svn commit -m
'added directory'
|
What does this let you do? Now you can get the latest version of any
file committed under this directory, delete files, rename them, create
new files or directories, commit changes to existing files, and more:
Listing 2. Basic file operations under
SVN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# get latest
% svn up
# what's the status?
% svn st
# delete files
% svn delete
# rename files
(really a delete + add that keeps history)
% svn rename
# make directory
% svn mkdir
# add file
% svn add
# commit changes
(everything above, plus any content changes)
% svn commit
|
I won't examine these commands in detail here, but do keep them in
mind. For help on any of these commands, just type
svn help COMMAND
,
and Subversion will show you some basic help; go to the manual for more.
Keeping a directory under Git
I'll follow the same path as I did with the SVN example. As before,
I'm assuming you already have a directory full of data.
For the remote server, I'll use the free github.com service, but, of
course, you can set up your own server if you like. GitHub is an easy way
to play with a remote Git repository. As of this writing, for a free
account you're limited to 300MB of data and your repository must be
public. I signed up as user "tzz" and created a public repository called
"datatest"; feel free to use it. I gave my public SSH key; you should
generate one if you don't have one already. You may also want to try the
Gitorious server or repo.or.cz. You'll find a long list of Git hosting
services on the git.or.cz Wiki (see
Related topics
for a link).
One nice thing about GitHub is that it's friendly. It tells you
exactly what commands are needed to set up Git and initialize the
repository. I'll walk through those with you.
First, you need to install Git, which is different on every platform,
and then initialize it. The Git download page (see
Related topics
) lists a number of options depending on platform. (On
Mac OS X, I used the
port install git-core
command, but you
need to set up MacPorts first. There's also a standalone MacOS X Git
installer linked from the Git download page; that will probably work
better for most people.)
Once you have it installed, here are the commands I used for a basic
setup (pick your own user name and e-mail address, naturally):
Listing 3. Basic Git setup
1
2
|
% git config --global
user.name "Ted Zlatanov"
% git config --global
user.email "[email protected]"
|
Already you might see a difference from SVN; there, your user identity
was server-side and you were whomever the server said you were. In Git,
you can be
The Wonderful Monkey Of Wittgenstein
if you want (I
resisted the temptation).
Next, I set up the data files and initialize my repository with them.
(GitHub will also import from a public SVN repository, which can be
helpful.)
Listing 4. Directory setup and first
commit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# grab some files
% cp -rp ~/.gdbinit
gdbinit
% mkdir fortunes
% cp -rp
~/.fortunes.db fortunes/data.txt
# initialize
% git init
# "Initialized empty
Git repository in /Users/tzz/datatest/.git/"
# add the file and
the directory
% git add gdbinit
fortunes
% git commit -m
'initializing'
#[master
(root-commit) b238ddc] initializing
# 2 files changed,
2371 insertions(+), 0 deletions(-)
# create mode 100644
fortunes/data.txt
# create mode 100644
gdbinit
|
In the output above, Git is telling us about file modes;
100644
refers to the octal version of the permission bits on those files. You
don't need to worry about that, but the
2371 insertions
is
puzzling. It only changed two files, right? That number actually refers
to the number of lines inserted. We didn't delete any, of course.
How about pushing our new changes to the GitHub server? The docs tell
us how to add a remote server called "origin" (you can use any name). I
should mention here that if you want to learn more about any Git command,
for example,
git remote
, you'd type
git remote --help
or
git help remote
. This is typical for command-line tools,
and SVN does something very similar.
Listing 5. Push the changes to the
remote
1
2
3
4
5
6
7
8
9
10
11
12
|
# remember the remote
repository is called "datatest"?
% git remote add
origin [email protected]:tzz/datatest.git
# push the changes
% git push origin
master
#Warning: Permanently
added 'github.com,65.74.177.129' (RSA) to the list
of known hosts.
#Counting objects: 5,
done.
#Delta compression
using 2 threads.
#Compressing objects:
100% (4/4), done.
#Writing objects:
100% (5/5), 29.88 KiB, done.
#Total 5 (delta 0),
reused 0 (delta 0)
#To
[email protected]:tzz/datatest.git
# * [new branch]
master -> master
|
The warning is from OpenSSH because github.com was not a known host
before. Nothing to worry about.
Git messages are, shall we say,
thorough
. Unlike SVN's
messages, which are easy to understand, Git is written for mentats, by
mentats. If you're from Frank Herbert's
Dune
universe and are
trained as a human computer, you've probably already written your own
version of Git, just because you can. For the rest of us, delta
compression and the number of threads used by it are just not very
relevant (and they make our heads hurt).
The push was done over SSH, but you can use other protocols, such as
HTTP, HTTPS, rsync, and file. See
git push --help
.
Here is the crucial, most important, basic difference between SVN and
Git. SVN's commit says "push this to the central server." Until you
commit in SVN, your changes are ethereal. With Git, your commit is
local
, and you have a local repository no matter what happens on the
remote side. You can back out a change, branch, commit to the branch, and
so on without any interaction with the remote server. Pushing with Git is
effectively a synchronization of your repository's state with the remote
server.
All right, so finally let's see the Git log of what just happened:
Listing 6. The Git log
1
2
3
4
5
6
|
% git log
#commit
b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov
<[email protected]>
#Date: ...commit
date here...
#
# initializing
|
Only the commit is in the log (note the long, random-looking commit ID
as opposed to the SVN revision number). There is no mention of the
synchronization via
git push
.
Collaborating through Git
So far we've been using Git as a SVN replacement. Of course, to make
it interesting, we have to get multiple users and changesets involved.
I'll check out the repository to another machine (running Ubuntu
GNU/Linux in this case; you'll need to install
git-core
and
not
git
):
Listing 7. Setting up another Git
identity and checking out the repository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
% git config --global
user.name "The Other Ted"
% git config --global
user.email "[email protected]"
% git clone
[email protected]:tzz/datatest.git
#Initialized empty
Git repository in /home/tzz/datatest/.git/
#Warning: Permanently
added 'github.com,65.74.177.129' (RSA) to the list
of known hosts.
#remote: Counting
objects: 5, done.
#remote: Compressing
objects: 100% (4/4), done.
#Indexing 5
objects...
#remote: Total 5
(delta 0), reused 0 (delta 0)
# 100% (5/5) done
% ls datatest
#fortunes gdbinit
% ls -a datatest/.git
# . .. branches
config description HEAD hooks index info logs
objects refs
% ls -a
datatest/.git/hooks
# . ..
applypatch-msg commit-msg post-commit
post-receive post-update
# pre-applypatch
pre-commit pre-rebase update
|
Again, notice the OpenSSH warning indicating we have not done business
with GitHub over SSH before from this machine. The
git clone
command is like a SVN checkout, but instead of getting a synthesized
version of the contents (a snapshot as of a particular revision, or the
latest revision), you are getting the whole repository.
I included the contents of the datatest/.git directory and the hooks
subdirectory under it to show that you really do get everything. Git
keeps no secrets by default, unlike SVN, which keeps the repository
private by default and only allows access to snapshots.
Incidentally, if you want to enforce some rules on your Git
repository, whether on every commit or at other times, the hooks are the
place. They are shell scripts, much like the SVN hooks, and have the same
"return zero for success, anything else for failure" standard UNIX
convention. I won't go into more detail on hooks here, but if your
ambition is to use Git in a team, you should definitely read up on them.
All right, so "The Other Ted" is frisky and wants to add a new file in
the master branch (roughly equivalent to SVN's TRUNK) and also make a new
branch with some changes to the gdbinit file.
Listing 8. Adding a file and making a
new branch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
# get a file to
add...
% cp ~/bin/encode.pl
.
% git add encode.pl
% git commit -m
'adding encode.pl'
#Created commit
6750342: adding encode.pl
# 1 files changed, 1
insertions(+), 0 deletions(-)
# create mode 100644
encode.pl
% git log
#commit
675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other
Ted <[email protected]>
#Date: ...commit
date here...
#
# adding encode.pl
#
#commit
b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov
<[email protected]>
#Date: ...commit
date here...
#
# initializing
% git branch
empty-gdbinit
% git branch
# empty-gdbinit
#* master
% git checkout
empty-gdbinit
#Switched to branch
"empty-gdbinit"
% git branch
#* empty-gdbinit
# master
% git add gdbinit
% git commit -m
'empty gdbinit'
#Created commit
5512d0a: empty gdbinit
# 1 files changed, 0
insertions(+), 1005 deletions(-)
% git push
#updating
'refs/heads/master'
# from
b238ddca99ee582e1a184658405e2a825f0815da
# to
675034202629e5497ed10b319a9ba42fc72b33e9
#Generating pack...
#Done counting 4
objects.
#Result has 3
objects.
#Deltifying 3
objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 0),
reused 0 (delta 0)
|
That was a long example and I hope you didn't fall asleep; if you did,
I hope you dreamt of Git repositories synchronizing in an endless waltz
of changesets. (Oh, you'll have those dreams, don't worry.)
First, I added a file (encode.pl, only one line) and committed it.
After the commit, the remote repository at GitHub did not have any idea I
had made changes. I then made a new branch called
empty-gdbinit
and switched to it (I could have done this with
git checkout -b
empty-gdbinit
as well). In that branch, I emptied the gdbinit file
and committed that change. Finally, I pushed to the remote server.
If I switch to the master branch, I won't see the empty gdbinit in the
logs. So, each branch has its own log, which makes sense.
Listing 9. Looking at logs between
branches
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
# we are still in the
empty-gdbinit branch
% git log
#commit
5512d0a4327416c499dcb5f72c3f4f6a257d209f
#Author: The Other
Ted <[email protected]>
#Date: ...commit
date here...
#
# empty gdbinit
#
#commit
675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other
Ted <[email protected]>
#Date: ...commit
date here...
#
# adding encode.pl
#
#commit
b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov
<[email protected]>
#Date: ...commit
date here...
#
# initializing
% git checkout master
#Switched to branch
"master"
% git log
#commit
675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other
Ted <[email protected]>
#Date: ...commit
date here...
#
# adding encode.pl
#
#commit
b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov
<[email protected]>
#Date: ...commit
date here...
#
# initializing
|
When I did the push, Git said, "Hey, look at that, a new file called
encode.pl" on GitHub's servers.
GitHub's Web interface will now display encode.pl. But there's still
only one branch on GitHub. Why was the
empty-gdbinit
branch
not synchronized? It's because Git doesn't assume you want to push
branches and their changes by default. For that, you need to push
everything:
Listing 10. Pushing all
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
% git push -a
#updating
'refs/heads/empty-gdbinit'
# from
0000000000000000000000000000000000000000
# to
5512d0a4327416c499dcb5f72c3f4f6a257d209f
#updating
'refs/remotes/origin/HEAD'
# from
0000000000000000000000000000000000000000
# to
b238ddca99ee582e1a184658405e2a825f0815da
#updating
'refs/remotes/origin/master'
# from
0000000000000000000000000000000000000000
# to
b238ddca99ee582e1a184658405e2a825f0815da
#Generating pack...
#Done counting 5
objects.
#Result has 3
objects.
#Deltifying 3
objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 1),
reused 0 (delta 0)
|
Again, the mentat interface is here in full glory. But we can figure
things out, right? We may not be mentats, but at least we have the common
sense to figure that
0000000000000000000000000000000000000000
is some kind of special initial tag. We can also see from the logs in
Listing 9
that tag
5512d0a4327416c499dcb5f72c3f4f6a257d209f
is the last (and only) commit in the
empty-gdbinit
branch.
The rest might as well be in Aramaic for most users; they just won't
care. GitHub will now show the new branch and the changes in it.
You can use
git mv
and
git rm
to manage
files, renaming and removing them, respectively.
Conclusion
In this article, I explained basic Git concepts and used Git to keep a
simple directory's contents under version control, comparing Git with
Subversion along the way. I explained branching using a simple example.
In Part 2, I will explore merging, generating diffs, and some of the
other Git commands. I strongly encourage you to read the very
understandable Git manual or at least go through the tutorial. It's all
accessible from the Git home page, so please spend some time exploring
it. (See the
Related topics
below for links.) From the perspective of a SVN user,
you don't need much more.
On the other hand, Git is a very rich DVCS; learning more about its
features will almost certainly lead to using them to simplify and improve
your VCS workflow. Plus, you may even have a dream or two about Git
repositories.
This is the second of a two-part series. You
should read the
Part 1
if you haven't already, as I'll use
the same Git and Subversion (SVN) setup, and it
will get you used to my sense of humor.
Branching and merging in SVN
Easily the greatest source of headaches for
version control system (VCS) managers are
branching
and
merging
. The vast
majority of developers prefer to commit all of
their changes in the trunk. As soon as branching
and merging come up, developers start to
complain, and the VCS manager gets to deal with
it.
To be fair to developers, branching and
merging are scary operations. The results are not
always obvious, and merging can cause problems by
undoing other people's work.
SVN manages the trunk well and many developers
don't bother with branching. SVN clients before
1.5 were a bit primitive about tracking merges,
so if you're used to older SVN clients, you might
not know about SVN's
svn:mergeinfo
property.
There's also a tool called svnmerge.py (see
Related topics
for a link). svnmerge.py can
track merges without the
svn:mergeinfo
support and thus works for older SVN clients.
Because of the complexity and variations in
SVN's merge support, I won't provide specific
examples. Instead, let's just talk about Git's
branch merging. You can read the SVN manual
referenced in the
Related topics
section if you are interested.
Branching and
merging in Git
If Concurrent Versions System (CVS) is the
village idiot when it comes to branching and
merging, SVN is the vicar and Git is the mayor.
Git was practically designed to support easy
branching and merging. This Git feature not only
impresses in the demo but is also handy every
day.
To give you an example, Git has multiple merge
strategies, including one called the
octopus
strategy, which allows you to merge multiple
branches at once. An octopus strategy! Just think
about the insanity of attempting to do this kind
of merge in CVS or SVN. Git also supports a
different kind of merging called
rebasing
.
I won't examine rebasing here, but it is quite
helpful for simplifying the repository history,
so you might want to look it up.
Before I proceed with the merge example below,
you should be familiar with the branch setup in
Part 1
. You have
HEAD
(the
current branch, in this case
master
)
and the
empty-gdbinit
branch. First,
let's merge
empty-gdbinit
into
HEAD
, then make a change in
HEAD
and merge it the other way into
empty-gdbinit
:
Listing 1.
Merging changes from branch to HEAD with Git
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# start clean
% git clone
[email protected]:tzz/datatest.git
# ...clone output...
# what branches are
available?
% git branch -a
#* master
# origin/HEAD
# origin/empty-gdbinit
# origin/master
# do the merge
% git merge
origin/empty-gdbinit
#Updating 6750342..5512d0a
#Fast forward
# gdbinit | 1005
---------------------------------------------------------------
# 1 files changed, 0
insertions(+), 1005
deletions(-)
# now push the merge to the
server
% git push
#Total 0 (delta 0), reused 0
(delta 0)
#To
[email protected]:tzz/datatest.git
# 6750342..5512d0a master
-> master
|
This is not hard as long as you realize that
master
has
HEAD
, and
after the merge with the
empty-gdbinit
branch, the
master
branch gets
pushed to the remote server to synchronize with
origin/master
. In other words, you
merged locally from a remote branch and then
pushed the result to another remote branch.
What's important here is to see how Git does
not care which branch is authoritative. You can
merge from a local branch to another local branch
or to a remote branch. The Git server only gets
involved for remote operations. In contrast, SVN
always requires the SVN server, because with SVN
the repository on the server is the only
authoritative version.
Of course, Git is a distributed VCS, so none
of this is surprising. It was designed to work
without central authority. Still, the freedom can
be a bit jarring to developers used to CVS and
SVN.
Now, properly prepared with all this grand
talk, let's make another local branch:
Listing 2.
Creating and switching to a release branch on
machine A
1
2
3
4
5
6
7
8
9
10
11
|
# create and switch to the
stable branch
% git checkout -b
release-stable
#Switched to a new branch
"release-stable"
% git branch
# master
#* release-stable
# push the new branch to the
origin
% git push --all
#Total 0 (delta 0), reused 0
(delta 0)
#To
[email protected]:tzz/datatest.git
# * [new branch]
release-stable ->
release-stable
|
Now, on a different machine we will remove the
gdbinit
file from the master branch.
Of course, it doesn't have to be a different
machine, it can simply be in a different
directory, but I'm reusing "The Other Ted"
identity on Ubuntu from
Part 1
for machine B.
Listing 3.
Removing gdbinit from master branch on machine B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# start clean
% git clone
[email protected]:tzz/datatest.git
# ...clone output...
% git rm gdbinit
# rm 'gdbinit'
# hey, what branch am I in?
% git branch
#* master
# all right, commit my
changes
% git commit -m "removed
gdbinit"
#Created commit 259e0fd:
removed gdbinit
# 1 files changed, 0
insertions(+), 1
deletions(-)
# delete mode 100644 gdbinit
# and now push the change to
the remote branch
% git push
#updating
'refs/heads/master'
# from
5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to
259e0fda9a8e9f3b0a4b3019781b99a914891150
#Generating pack...
#Done counting 3 objects.
#Result has 2 objects.
#Deltifying 2 objects...
# 100% (2/2) done
#Writing 2 objects...
# 100% (2/2) done
#Total 2 (delta 1), reused 0
(delta 0)
|
Nothing crazy here (except for "deltifying,"
which sounds like something you'd do at the gym
or something a river might do near a large body
of water). But what happens on machine A in the
release-stable
branch?
Listing 4.
Merging removal of gdbinit from master branch to
release-stable branch on machine A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# remember, we're in the
release-stable branch
% git branch
# master
#* release-stable
# what's different vs. the
master?
% git diff origin/master
#diff --git a/gdbinit
b/gdbinit
#new file mode 100644
#index 0000000..8b13789
#--- /dev/null
#+++ b/gdbinit
#@@ -0,0 +1 @@
#+
# pull in the changes
(removal of gdbinit)
% git pull origin master
#From
[email protected]:tzz/datatest
# * branch
master -> FETCH_HEAD
#Updating 5512d0a..259e0fd
#Fast forward
# gdbinit | 1 -
# 1 files changed, 0
insertions(+), 1
deletions(-)
# delete mode 100644 gdbinit
# push the changes to the
remote server (updating the
remote release-stable
branch)
% git push
#Total 0 (delta 0), reused 0
(delta 0)
#To
[email protected]:tzz/datatest.git
# 5512d0a..259e0fd
release-stable ->
release-stable
|
The mentat interface, which I referred to in
Part 1
, strikes again in the diff. You're
supposed to know that
/dev/null
is a
special file that contains nothing, and thus the
remote master branch has nothing, whereas the
local
release-stable
branch has the
gdbinit
file. That's not always
obvious to most users.
After all that fun, the
pull
merges the local branch with
origin/master
and then the
push
updates
origin/release-stable
with the changes. As
usual, "delta" is the Git developer's favorite
word-one never misses a chance to use it.
Bisecting changes
I won't go into the
git bisect
command in detail here, because it is quite
complicated, but I wanted to mention it because
it is a terrific tool. Bisecting changes is
really a binary search across the commit log.
"Binary" means that the search splits the search
interval down the middle and tests the middle
each time to decide if the wanted segment is
above or below the middle.
The way it works is simple. You tell Git that
version A is good and version Z is bad. Git then
asks you (or asks an automated script) if the
version halfway between A and Z, say Q, is bad.
If Q is bad, then the bad commit is between A and
Q; otherwise the bad commit is between Q and Z.
The process is repeated until the bad commit is
found.
It's especially nice that bisecting can be
automated with a test script. That makes it
possible to write a test for version Z and use it
backwards to find when a feature broke, which
most developers would call an
automated
regression test
. Those
will
save
you time.
Resolving
conflicts
Merge conflicts are inevitable in any VCS and
especially so in a distributed VCS such as Git.
What happens if two people change a file in
conflicting ways in the same branch? Both of the
following examples are in the master branch of
the datatest repository we've been using so far.
First we make a change to encode.pl on machine
B:
Listing 5. "Does
not work" on machine B
1
2
3
4
5
6
7
8
9
10
|
# we're at time T1
# change the contents
% echo "# this script
doesn't work" > encode.pl
% git commit -a -m 'does not
work'
#Created commit e61713b:
does not work
# 1 files changed, 1
insertions(+), 1
deletions(-)
# we're at time T2 now,
what's our status?
% git status
# On branch master
#nothing to commit (working
directory clean)
|
Now we make a change to encode.pl on machine A
without awareness of the changes on machine B,
and
push
it:
Listing 6. "Does
work" on machine A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# we're at time T2
# change the contents
% echo "this script does
work" > encode.pl
% git commit -a -m 'does not
work'
#Created commit e61713b:
does not work
# 1 files changed, 1
insertions(+), 1
deletions(-)
# we're at time T3 now,
what's our status?
% git status
# On branch master
# Your branch is ahead of
'origin/master' by 1 commit.
#
#nothing to commit (working
directory clean)
% git push
#Counting objects: 5, done.
#Delta compression using 2
threads.
#Compressing objects: 100%
(2/2), done.
#Writing objects: 100%
(3/3), 298 bytes, done.
#Total 3 (delta 0), reused 0
(delta 0)
#To
[email protected]:tzz/datatest.git
# 259e0fd..f949703 master
-> master
|
Now, on machine B, we do a
git pull
and realize things are not so wonderful:
Listing 7. Uh-oh
on machine B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
% git pull
#remote: Counting objects:
5, done.
#Compressing objects: 100%
(2/2), done.)
#remote: Total 3 (delta 0),
reused 0 (delta 0)
#Unpacking 3 objects...
# 100% (3/3) done
#*
refs/remotes/origin/master:
fast forward to branch
'master'
# of
[email protected]:tzz/datatest
# old..new:
259e0fd..f949703
#Auto-merged encode.pl
#CONFLICT (content): Merge
conflict in encode.pl
#Automatic merge failed; fix
conflicts and then commit
the result.
# the next command is
optional
% echo uh-oh
#uh-oh
# you can also use "git
diff" to see the conflicts
% cat encode.pl
#<<<<<<< HEAD:encode.pl
## this script doesn't work
#=======
#this script works
#>>>>>>>
f9497037ce14f87ff984c1391b6811507a4dd86c:encode.pl
|
This situation is very common in SVN as well.
Someone else's changes disagree with your version
of a file. Just edit the file and commit:
Listing 8.
Fixing and committing on machine B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# fix encode.pl before this
to contain only "# this
script doesn't work"...
% echo "# this script
doesn't work" > encode.pl
# commit, conflict resolved
% git commit -a -m ''
#Created commit 05ecdf1:
Merge branch 'master' of
[email protected]:tzz/datatest
% git push
#updating
'refs/heads/master'
# from
f9497037ce14f87ff984c1391b6811507a4dd86c
# to
05ecdf164f17cd416f356385ce8f5c491b40bf01
#updating
'refs/remotes/origin/HEAD'
# from
5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to
f9497037ce14f87ff984c1391b6811507a4dd86c
#updating
'refs/remotes/origin/master'
# from
5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to
f9497037ce14f87ff984c1391b6811507a4dd86c
#Generating pack...
#Done counting 8 objects.
#Result has 4 objects.
#Deltifying 4 objects...
# 100% (4/4) done
#Writing 4 objects...
# 100% (4/4) done
#Total 4 (delta 0), reused 0
(delta 0)
|
That was easy, wasn't it? Let's see what
happens on machine A next time it updates.
Listing 9.
Fixing and committing on machine B
1
2
3
4
5
6
7
8
9
10
11
12
13
|
% git pull
#remote: Counting objects:
8, done.
#remote: Compressing
objects: 100% (3/3), done.
#remote: Total 4 (delta 0),
reused 0 (delta 0)
#Unpacking objects: 100%
(4/4), done.
#From [email protected]:tzz/datatest
# f949703..05ecdf1
master -> origin/master
#Updating f949703..05ecdf1
#Fast forward
# encode.pl | 2 +-
# 1 files changed, 1
insertions(+), 1
deletions(-)
% cat encode.pl
## this script doesn't work
|
Fast forward
means that the local
branch caught up with the remote branch
automatically, because it contained nothing new
to the remote branch. In other words, a fast
forward implies no merging was needed; all the
local files were no newer than the remote
branch's latest push.
Finally, I should mention
git revert
and
git reset
, which are very useful
for undoing a commit or other changes to the Git
tree. There's no room to explain them here, but
make sure you know how to use them.
Conclusion
This article opened up the concept of merging,
showing what it's like to keep the local and
remote branches on two machines and resolving
conflicts between them. I also drew attention to
the complicated, even arcane Git messages,
because compared with SVN, Git is much more
verbose and much less intelligible. When you
couple this fact with the complex syntax of Git's
commands, it can make Git pretty intimidating for
most beginners. However, once a few basic
concepts are explained, Git gets much easier-even
pleasant!
Downloadable
resources
Related
topics
- "
Git
for Subversion users, Part 1: Getting started
"
(developerWorks, August 2009) shows how to
install Git and set up a repository.
- In the SVN manual, read all you ever
wanted to know about
SVN branching and merging
.
- Learn more about the Git merge option,
rebasing
.
- In case you were caught short on this bit
of trivia, read the Wikipedia entry on
mentats
.
- This
crash course tutorial on Git
is a good
place to find more examples. Another tutorial
worth reading is Flavio Castelli's "
Howto
use Git and svn together
."
- For Git hosting resources, try
GitHub
,
Gitorious
,
repo.or.cz
,
and this
list of public Git hosting sites
.
- The Wikipedia community's take on
Git
and
Subversion
are both quite thorough.
-
svnmerge.py
is a tool to automate merge
tracking. It allows branch maintainers to
merge changes from and to their branches
easily, and it automatically records which
changes were already merged.
-
Git
downloads, documentation, and tools
are
available from the Git Web site.
Related topics
- The
Git - SVN Crash
Course
is a handy reference for those already familiar with SVN.
Another good tutorial is Flavio Castelli's
Howto use
Git and SVN together
.
- For Git hosting resources, try
GitHub
,
Gitorious
,
repo.or.cz
, and this
list of public Git
hosting sites
.
-
Wikipedia's take on Subversion
is quite full-featured, as is the
entry on
Git
.
- Hear from Robby Russell, who
migrated from Subversion to Git
and actually lived to tell about
it.
- Get
Git
, and see the
Git download page
, as well
as tons of documentation and tools.
- In the
developerWorks Linux zone
, find more resources for Linux
developers, and scan our
most popular articles and tutorials
.
- See all
Linux tips
and
Linux tutorials
on developerWorks.
The
Git - SVN Crash Course
is a handy reference
for those already familiar with SVN. Another good tutorial is Flavio Castelli's
Howto use Git and SVN together
.
!--file:///F:/Public_html/SE/Conf_management/Git/index.shtml-->
git branch
A branch represents an independent line of development. Branches serve as an abstraction for the
edit/stage/commit process discussed in
Git Basics ,
the first module of this series. You can think of them as a way to request a brand new working directory,
staging area, and project history. New commits are recorded in the history for the current branch,
which results in a fork in the history of the project.
The git branch
command lets you create, list, rename, and delete branches. It doesn't
let you switch between branches or put a forked history back together again. For this reason,
git branch
is tightly integrated with the
git checkout
and
git merge
commands.
Usage
git branch
List all of the branches in your repository.
git branch <branch>
Create a new branch called <branch>
. This does not check out the new branch.
git branch -d <branch>
Delete the specified branch. This is a "safe" operation in that Git prevents you from deleting
the branch if it has unmerged changes.
git branch -D <branch>
Force delete the specified branch, even if it has unmerged changes. This is the command to use
if you want to permanently throw away all of the commits associated with a particular line of development.
git branch -m <branch>
Rename the current branch to <branch>
.
Discussion
In Git, branches are a part of your everyday development process. When you want to add a new feature
or fix a bug-no matter how big or how small-you spawn a new branch to encapsulate your changes. This
makes sure that unstable code is never committed to the main code base, and it gives you the chance
to clean up your feature's history before merging it into the main branch.
For example, the diagram above visualizes a repository with two isolated lines of development,
one for a little feature, and one for a longer-running feature. By developing them in branches, it's
not only possible to work on both of them in parallel, but it also keeps the main master branch
free from questionable code.
Branch Tips
The implementation behind Git branches is much more lightweight than SVN's model. Instead of copying
files from directory to directory, Git stores a branch as a reference to a commit. In this sense,
a branch represents the tip of a series of commits-it's not a container for commits.
The history for a branch is extrapolated through the commit relationships.
This has a dramatic impact on Git's merging model. Whereas merges in SVN are done on a file-basis,
Git lets you work on the more abstract level of commits. You can actually see merges in the project
history as a joining of two independent commit histories.
Example Creating Branches
It's important to understand that branches are just pointers to commits. When you create
a branch, all Git needs to do is create a new pointer-it doesn't change the repository in any other
way. So, if you start with a repository that looks like this:
Then, you create a branch using the following command:
git branch crazy-experiment
The repository history remains unchanged. All you get is a new pointer to the current commit:
Note that this only creates the new branch. To start adding commits to it, you need to
select it with git checkout
, and then use the standard git add
and
git commit
commands. Please see the
git checkout
section of this module for more information.
Deleting Branches
Once you've finished working on a branch and have merged it into the main code base, you're free
to delete the branch without losing any history:
git branch -d crazy-experiment
However, if the branch hasn't been merged, the above command will output an error message:
error: The branch 'crazy-experiment' is not fully merged. If you are sure you want to delete it, run 'git branch -D crazy-experiment'.
This protects you from losing your reference to those commits, which means you would effectively
lose access to that entire line of development. If you really want to delete the branch
(e.g., it's a failed experiment), you can use the capital -D
flag:
git branch -D crazy-experiment
This deletes the branch regardless of its status and without warnings, so use it judiciously.
git checkout
The git checkout
command lets you navigate between the branches created by
git branch
. Checking out a branch updates the files in the working directory to match the
version stored in that branch, and it tells Git to record all new commits on that branch. Think of
it as a way to select which line of development you're working on.
In the previous
module , we saw how git checkout
can be used to view old commits. Checking out branches
is similar in that the working directory is updated to match the selected branch/revision; however,
new changes are saved in the project history-that is, it's not a read-only operation.
Usage
git checkout <existing-branch>
Check out the specified branch, which should have already been created with git branch
. This makes <existing-branch> the current branch, and updates the working directory to match.
git checkout -b <new-branch>
Create and check out <new-branch>. The -b
option is a convenience flag that tells
Git to run git branch <new-branch>
before running git checkout <new-branch>
. git checkout -b <new-branch> <existing-branch>
Same as the above invocation, but base the new branch off of <existing-branch> instead of the
current branch.
Discussion
git checkout
works hand-in-hand with git branch
. When you want to start
a new feature, you create a branch with git branch
, then check it out with git
checkout
. You can work on multiple features in a single repository by switching between them
with git checkout
.
Having a dedicated branch for each new feature is a dramatic shift from the traditional SVN workflow.
It makes it ridiculously easy to try new experiments without the fear of destroying existing functionality,
and it makes it possible to work on many unrelated features at the same time. In addition, branches
also facilitate several collaborative workflows.
Detached HEADs
Now that we've seen the three main uses of git checkout
we can talk about that "detached
HEAD
" we encountered in the previous module.
Remember that the HEAD
is Git's way of referring to the current snapshot. Internally,
the git checkout
command simply updates the HEAD
to point to either the
specified branch or commit. When it points to a branch, Git doesn't complain, but when you check
out a commit, it switches into a "detached HEAD
" state.
This is a warning telling you that everything you're doing is "detached" from the rest of your
project's development. If you were to start developing a feature while in a detached HEAD
state, there would be no branch allowing you to get back to it. When you inevitably check
out another branch (e.g., to merge your feature in), there would be no way to reference your feature:
The point is, your development should always take place on a branch-never on a detached
HEAD
. This makes sure you always have a reference to your new commits. However, if you're
just looking at an old commit, it doesn't really matter if you're in a detached HEAD
state or not.
Example
The following example demonstrates the basic Git branching process. When you want to start working
on a new feature, you create a dedicated branch and switch into it:
git branch new-feature git checkout new-feature
Then, you can commit new snapshots just like we've seen in previous modules:
# Edit some files git add <file> git commit -m "Started work on a new feature" # Repeat
All of these are recorded in new-feature
, which is completely isolated from
master
. You can add as many commits here as necessary without worrying about what's
going on in the rest of your branches. When it's time to get back to "official" code base, simply
check out the master
branch:
git checkout master
This shows you the state of the repository before you started your feature. From here, you have
the option to merge in the completed feature, branch off a brand new, unrelated feature, or do some
work with the stable version of your project.
Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure,
brought to you in partnership with
Sauce Labs .
This is the script for a talk that I gave at
BarCamp
Philly . The talk is a "live committing" exercise, and this post contains all the information
needed to follow along, as well as some links to relevant source material and minus my pauses, typos,
and attempts at humor.
Object Management
I think that the first thing to understand about Git is that it's not strictly a source control
system; it's more like a versioned filesystem that happens to be good at source control. Traditionally,
source control systems focused on the evolution of files. For example, RCS (and its successor CVS)
maintain a separate file in the repository for each source file. These repository files hold the
entire history of the file as a sequence of diffs that allow the tool to reconstruct any version.
Subversion applies the idea of diffs to the entire repository, allowing it to track files as they
move between directories.
Git takes a different approach. Rather than constructing the state of the repository via diffs,
it maintains snapshots of the repository and constructs diffs from those (if you don't believe this,
read on). This allows very efficient comparisons between any two points in history but does consume
more disk space. I think the key insight is not just that disk is cheap and programmer time expensive,
but that real-world software projects don't have a lot of large files, and those files don't experience
a lot of churn.
To see Git in action, we'll create a temporary directory, initialize it as a repository, and create
a couple of files. I should note here that I'm using bash
on Linux; if you're running
Windows you're on your own re commands. Anything that starts with " >
" is a command
that I typed; anything else is the response from the system.
... ... ...
So, what does it mean that git log
produces the illusion of a series of merged commits?
Consider what happens when you check out one of the commits in the list, for example 2e68e9b9
.
This was a commit that was made on the branch. If you check out that commit and look at the commit
log from that point, you'll see that commit 5269b074
no longer appears. It was made
on master
, in a completely different chain of commits.
In a complex series of merges (say, multiple development branches onto an integration branch and
several integration branches onto a feature branch) you can completely lose track of where and why
a change was made. If you try to diff your way through the commit history, you'll find that the code
changes dramatically between commits, and appears to flip-flop. You're simply seeing the code state
on different branches.
Notable quotes:
"... entire contents ..."
The git add
command adds a change in the working directory to
the staging area. It tells Git that you want to include updates to a particular
file in the next commit. However, git add
doesn't really affect
the repository in any significant way-changes are not actually recorded until
you run
git commit
.
In conjunction with these commands, you'll also need
git status
to view the state of the working directory and the
staging area.
Usage
git add <file>
Stage all changes in <file>
for the next commit.
git add <directory>
Stage all changes in <directory>
for the next commit.
git add -p
Begin an interactive staging session that lets you choose portions of a file
to add to the next commit. This will present you with a chunk of changes and
prompt you for a command. Use y
to stage the chunk, n
to ignore the chunk, s
to split it into smaller chunks,
e
to manually edit the chunk, and q
to exit.
Discussion
The git add
and git commit
commands compose the
fundamental Git workflow. These are the two commands that every Git user needs
to understand, regardless of their team's collaboration model. They are the
means to record versions of a project into the repository's history.
Developing a project revolves around the basic edit/stage/commit pattern.
First, you edit your files in the working directory. When you're ready to save
a copy of the current state of the project, you stage changes with git
add
. After you're happy with the staged snapshot, you commit it to the
project history with git commit
.
The git add
command should not be confused with svn add
, which adds a file to the repository. Instead, git add
works on the more abstract level of changes . This means that
git add
needs to be called every time you alter a file, whereas
svn add
only needs to be called once for each file. It may sound redundant,
but this workflow makes it much easier to keep a project organized.
The Staging Area
The staging area is one of Git's more unique features, and it can take some
time to wrap your head around it if you're coming from an SVN (or even a Mercurial)
background. It helps to think of it as a buffer between the working directory
and the project history.
Instead of committing all of the changes you've made since the last commit,
the stage lets you group related changes into highly focused snapshots before
actually committing it to the project history. This means you can make all sorts
of edits to unrelated files, then go back and split them up into logical commits
by adding related changes to the stage and commit them piece-by-piece. As in
any revision control system, it's important to create atomic commits so that
it's easy to track down bugs and revert changes with minimal impact on the rest
of the project.
Example
When you're starting a new project, git add
serves the same
function as svn import
. To create an initial commit of the current
directory, use the following two commands:
git add . git commit
Once you've got your project up-and-running, new files can be added by passing
the path to git add
:
git add hello.py git commit
The above commands can also be used to record changes to existing files.
Again, Git doesn't differentiate between staging changes in new files vs. changes
in files that have already been added to the repository.
git commit
The git commit
command commits the staged snapshot to the project
history. Committed snapshots can be thought of as "safe" versions of a project-Git
will never change them unless you explicity ask it to. Along with git
add
, this is one of the most important Git commands.
While they share the same name, this command is nothing like svn commit
. Snapshots are committed to the local repository, and this requires
absolutely no interaction with other Git repositories.
Usage
git commit
Commit the staged snapshot. This will launch a text editor prompting you
for a commit message. After you've entered a message, save the file and close
the editor to create the actual commit. git commit -m "<message>"
Commit the staged snapshot, but instead of launching a text editor, use
<message>
as the commit message.
git commit -a
Commit a snapshot of all changes in the working directory. This only includes
modifications to tracked files (those that have been added with git add
at some point in their history).
Discussion
Snapshots are always committed to the local repository. This is
fundamentally different from SVN, wherein the working copy is committed to the
central repository. In contrast, Git doesn't force you to interact with the
central repository until you're ready. Just as the staging area is a buffer
between the working directory and the project history, each developer's local
repository is a buffer between their contributions and the central repository.
This changes the basic development model for Git users. Instead of making
a change and committing it directly to the central repo, Git developers have
the opportunity to accumulate commits in their local repo. This has many advantages
over SVN-style collaboration: it makes it easier to split up a feature into
atomic commits, keep related commits grouped together, and clean up local history
before publishing it to the central repository. It also lets developers work
in an isolated environment, deferring integration until they're at a convenient
break point.
Snapshots, Not Differences
Aside from the practical distinctions between SVN and Git, their underlying
implementation also follow entirely divergent design philosophies. Whereas SVN
tracks differences of a file, Git's version control model is based
on snapshots . For example, an SVN commit consists of a diff compared
to the original file added to the repository. Git, on the other hand, records
the entire contents of each file in every commit.
This makes many Git operations much faster than SVN, since a particular version
of a file doesn't have to be "assembled" from its diffs-the complete revision
of each file is immediately available from Git's internal database.
Git's snapshot model has a far-reaching impact on virtually every aspect
of its version control model, affecting everything from its branching and merging
tools to its collaboration workflows.
Example
The following example assumes you've edited some content in a file called
hello.py
and are ready to commit it to the project history. First,
you need to stage the file with git add
, then you can commit the
staged snapshot.
git add hello.py git commit
This will open a text editor (customizable via git config
)
asking for a commit message, along with a list of what's being committed:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit. # On branch
master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage)
# #modified: hello.py
Git doesn't require commit messages to follow any specific formatting constraints,
but the canonical format is to summarize the entire commit on the first line
in less than 50 characters, leave a blank line, then a detailed explanation
of what's been changed. For example:
Change the message displayed by hello.py - Update the sayHello() function
to output the user's name - Change the sayGoodbye() function to a friendlier
message
Note that many developers also like to use present tense in their commit
messages. This makes them read more like actions on the repository, which makes
many of the history-rewriting operations more intuitive.
Note: while the use-case described is about using submodules within a project, the same applies to a normal git clone
of a repository over HTTP.I have a project under Git control. I'd like to add a submodule:
git submodule add http://github.com/jscruggs/metric_fu.git vendor/plugins/metric_fu
But I get
...
got 1b0313f016d98e556396c91d08127c59722762d0
got 4c42d44a9221209293e5f3eb7e662a1571b09421
got b0d6414e3ca5c2fb4b95b7712c7edbf7d2becac7
error: Unable to find abc07fcf79aebed56497e3894c6c3c06046f913a under http://github.com/jscruggs/metri...
Cannot obtain needed commit abc07fcf79aebed56497e3894c6c3c06046f913a
while processing commit ee576543b3a0820cc966cc10cc41e6ffb3415658.
fatal: Fetch failed.
Clone of 'http://github.com/jscruggs/metric_fu.git' into submodule path 'vendor/plugins/metric_fu'
I have my HTTP_PROXY set up:
c:\project> echo %HTTP_PROXY%
http://proxy.mycompany:80
I even have a global Git setting for the http proxy:
c:\project> git config --get http.proxy
http://proxy.mycompany:80
Has anybody gotten HTTP fetches to consistently work through a proxy? What's really strange is that a few project on GitHub work
fine (awesome_nested_set
for example),
but others consistently fail (rails for example).
A:
You can also set the HTTP proxy that Git uses in global configuration property http.proxy
:C:\> git config --global http.proxy %HTTP_PROXY%
What finally worked was setting the $http_proxy
environment variable. I had set $HTTP_PROXY
correctly,
but git apparently likes the lower-case version better.
There's some great answers on this already. However, I thought I would chip in as some proxy servers require you to authenticate with
a user Id and password. Sometimes this can be on a domain.So, for example if your proxy server configuration is as follows:
Server: myproxyserver
Port: 8080
Username: mydomain\myusername
Password: mypassword
Then, add to your .gitconfig
file using the following command:
git config --global http.proxy http://mydomain\\myusername:mypassword@myproxyserver:8080
Don't worry about https
. As long as the specified proxy server supports http, and https, then one entry in the config
file will suffice.
You can then verify that the command added the entry to your .gitconfig
file successfully by doing cat .gitconfig
:
At the end of the file you will see an entry as follows:
[http]
proxy = http://mydomain\\myusername:mypassword@myproxyserver:8080
Softpanorama Recommended