Git: reset and checkout in one shot with && doesn't work - linux

Simple git question. After executing:
git reset HEAD file && git checkout -- file
The file is still under "Changes not staged for commit" section, but it shouldn't. If I execute the two operations seperately (ie. pressing enter and seeing git status between and after them), it works.
Platform: Linux amd64, git version 1.8.1.3

This is because git reset returns a non-zero exit code when the file has unstaged commits after the reset. So the && prevents the git checkout from running -- it will only run the second command if the first "succeeds" (i.e. exits with zero).

git reset HEAD file just updates the index (i.e., any staged changes to the file are lost), the changed file stays as is.
Why do it this way, if a simple git checkout file (perhaps with -f if file has been changed) acomplishes the same?

Related

Count number of mergeable commits using git

I have a script that auto merges a feature branch into master. It runs periodically.
I want the script to check that the feature branch does in fact have commits to be merged. If there are no commits the script should exit.
I’ve tried the following:
git rev-list --count HEAD
Run while on the feature branch called ‘test’ with no commits this is returning a value of 1, so the script doesn’t exit, even though it should because there are no commits to merge.
Note that I ran git fetch —all before all of these commands.
Also tried the following:
Tried:
git rev-list --count master..
Resulted in error:
fatal: ambiguous argument 'master..': unknown revision or path not in the working tree.
Tried:
git rev-list --count master
Resulted in error:
fatal: ambiguous argument 'master': unknown revision or path not in the working tree.
Tried:
git rev-list --count master..test
Resulted in error:
fatal: ambiguous argument 'master..test': unknown revision or path not in the working tree.
How do I determine (from a script) if there are commits to merge?
The command you are using is absolutely correct. The error you are seeing in your case means that master as a branch is not available in the local file system of the repository from where you are running this command.
To make this work properly, you'd want to put in the right reference.
git rev-list --count origin/master
Assuming that the remote you want to check against, is named origin.
Additionally you can do some shell magic to make it work like you want, the below works with zsh:
# merge branch
commit_distance=$(git rev-list --count origin/master)
if [ $commit_distance -eq 0 ];
then
exit 1
fi
# ... the remaining script

How to push without use git's command

After merging one branch into another, (like develop into master) with
git merge --no-ff develop
If you execute the git command git status in a terminal just after you will see:
On branch master
Your branch is ahead of 'origin/master' by 1 commits.
(use "git push" to publish your local commit)
nothing to commit, working tree clean
Which is not much different from that (if we look without paying attention):
On branch master
nothing to commit, working tree clean
Sometimes I do not pay much attention and I forget to push. So is there an command to know if we must push? or a way to color the Your branch is ahead of 'origin/master' by 1 commits. part in red?
or may be just a command that return 0 or 1 if we need to push, if yes I could include it with an echo routine in a git alias to make my git status more explicit.
Finally, I resolved it by putting this in my ~/.gitconfig:
[alias]
st = "!f() { git status -u; \
git status -u | grep \"Your branch is ahead\" > /dev/null \
&& echo \"\\e[31m[WARNING]\\e[91m You need to push :)\"; }; f"
Like that just by performing git st I will be warned by a red message if I need to push.

Git delete all unmodified files

I am using git in my project at Linux platform. I have plenty of files in a particular directory. I modified some 50 above files in that directory and didn't stage and commit it. I wish to delete all other unmodified files from that directory? Is there a way to do this, using git and Linux Commands?
Not sure why you would want to do this.... but you can:
# Save changes to stash
git stash save
# Remove everything left
rm -rf ./*
# Checkout (restore) all of the changed files
git stash show --stat | grep -v changed | sed -e 's/|.*$//;' | xargs git checkout
# Restore the changes to those files
git stash pop
git reset --hard [HEAD] should work for you repeated
Repeated question How can I discard modified files?
You can also use more simple commands for this purpose:
git clean -Xfd // capital X
git clean -xfd // lower x
It will clean your working directory from the desired files.
Using git clean is what you want. To remove (-x) those files and directories (-d), run:
$ git clean -fdx
If you use the -X option instead of -x, then the files you have told git to ignore will still be kept (e.g., build artifacts). Recent versions of git require either "-f" (force) or "-n" (dry-run) to be specified.
You should run a dry-run first, to show what will happen, but not actually do anything:
$ git clean -ndx
I use this so often, that I have an alias for this (added to your .gitconfig) to check for files that would be deleted when you run git clean. It's also useful to remind me if I've forgotten to "git add" a file that I want to keep.
[alias]
# list files that would be removed via 'clean' (non-destructive)
ifc = clean -ndx
Then, running git ifc (i.e,. "ifc" = "if clean") shows everything that isn't tracked and could be removed, or isn't tracked and should be added.
https://git-scm.com/docs/git-clean

Commit without add and how to see remote branch log

1.I'm new to git and would like to know what happen if I've a file which was modified and already staged in the past(but now modified again), and I want to commit the file using a command such as :
git commit -m "yada yada" ~/home/Dan/project/file_to_commit.py
Is this equivalent to doing:
a.git add ~/home/dan/project/file_to_commit.py
b.git commit -m "yada yada"
If not please explain.
2.How can I see the remotes branch commits logs?(pushes) without doing git pull?
Thanks
This might be better as two separate questions, and the second question is already answered correctly, but I'll take a stab at answering both. Before I start, though, I want to say that the path ~/home/Dan/project/file_to_commit.py is pretty suspect: it suggests that your git directory is /.git, which is not a good idea. I'm going to assume, below, that the .git directory is much further down and you're just adding project/file or file (and I'll trim the paths in the question).
Note that the TL;DR version of the first answer is that they're almost the same, and you only need to know about the difference in some edge cases. (Hence the existing answer from eleventhend is probably good enough for most purposes.)
Q1: Add and commit vs git commit path
... I've a file which was modified and already staged in the past (but now modified again), and I want to commit the file using a command such as:
git commit -m "yada yada" file_to_commit.py
Is this equivalent to doing:
git add file_to_commit.py
git commit -m "yada yada"
If not please explain.
No, it's not exactly equivalent. This is a little bit tricky and it will help a lot if we define some terms and get a few things pinned down a bit more.
Also, you say "already staged in the past (but now modified again)", which leaves a bit of ambiguity: did you do a git commit in between these operations? I'll address both the "yes" and "no" cases by describing the full, general case.
The index
First, we need to define git's index or staging area (it has even a few more names, e.g., cache as in git diff --cached, but "index" and "staging area" are the two most common terms). Git has a (one, single) standard index—let's call this "the" index, and when we want to refer to another, different index, we'll spell out which other one we mean. When you run most normal git commands, including git add, git updates this index.1
Next, we need to take some notes on what's actually in the index. Aside from some interesting but not relevant here cases like merges, and one thing I'm leaving out on purpose, in essence, the index has one entry per file that will be in the next commit.2 That is, suppose you've made a commit, or checked out some branch, so that your current commit, which is now in your work tree, has 100 files in it. (Your work tree may have additional untracked and/or ignored files, as long as it also has those 100 files.)
Using git add
When you run git add, git stores a new copy of each of the files being added into the repository, which it calls blob objects. It calculates a hash value for each blob as it adds it to the repository, then puts the new hash into the index.
When you run git commit—at least, without either paths or -a—git reads the index and uses that to form the new commit. If the new commit would have the same tree as the previous commit,3 git requires that you add the --allow-empty flag. (This doesn't mean that the index is empty, but rather that the index matches the index you'd get by re-checking-out the current commit. So --allow-empty might be the wrong name for this flag; it maybe should have been called --allow-same or allow-unchanged or some such.)
Hence, if you do git add path and then git commit -m message, you'll get a commit that uses the index as updated by the git add, which may have additional updates from before that git add. Since there's just the one entry per path, though, if you do:
... hack on foo.py ...
$ git add foo.py
$ echo '# add a final comment' >> foo.py
$ git add foo.py
$ git commit -m 'update foo'
there will only be one update to foo.py in the new commit.
So what's the difference?
I claimed above that git commit path (and git commit -a) is not exactly the same as doing the git add and then git commit. Let's look at the precise difference.
When you give path names (or -a) to git commit, git uses a new, different, temporary index. It starts by copying something—exactly what gets a bit complicated—to the temporary index, then it adds each file that is to be committed, then it makes a commit from the temporary index. Once the commit is done, git goes back to using the normal, ordinary index, and it updates the index. That is, after adding stuff to the temporary index and committing, it also adds to the regular index.
To see how this really works we need some examples. Suppose we have two files and we git add a change to one of them:
# assume file1 and file2 are in the HEAD commit
echo add stuff to file1 >> file1
git add file1
echo add stuff to file2 too >> file2
At this point, git status will tell us that we have changes to file1 that are staged to be committed, and changes to file2 that are not staged to be committed.
If we run git add file2; git commit, we'll get both updates in the new commit. Once the commit is done, git status will show there is nothing to commit. But if, instead, we do:
git commit -m message file2
and then run git status, we'll see that file1 is still staged and ready to commit. The commit we just made has only the change to file2.
This is because the git commit file2 command started by making a temporary index using the HEAD commit, adding file2, making the commit, and then updating the normal index with the new file2. This last bit is important: if git didn't copy the change back into the (regular) index, the index would still have the old version of file2, and the next commit would undo the change we just committed.
This copy-back step has an important side effect. Suppose that we have a complicated change to foo.py. For instance, suppose we went to fix a bug, and along the way we refactored a few functions. We've tested the fix and it works, so we did git add foo.py and were about to commit it:
... hack on foo.py ...
... test, hack more, test, until fixed ...
git add foo.py
git commit -m 'refactor foo.py and then fix a bug'^C
At this point we realized that we shouldn't commit both changes together: we should commit the refactored code first, and then commit the bug fix.4
Well, we've already git add-ed the refactored and fixed code, so it's safely stashed away in the index, right? (No, WRONG! Danger Will Robinson! But let's proceed, since this is an example.) So we can just undo the fix part, leaving only the refactoring, and commit that first:
... edit foo.py to remove just the fix ...
git commit -m 'refactor code to prep for bug fix' foo.py
Once that commit is done, we can commit the staged version:
git commit -m 'fix bug #12345' foo.py
Alas, git now tells us that there's nothing to commit. What happened to the carefully staged full-fix version of foo.py?
The answer is, git commit foo.py wiped it out. Git first added the refactor-only foo.py to a temporary index and committed that; but then it copied the refactor-only foo.py back to the regular index, losing our carefully staged full-fix version. We can either re-create it, or fish around in the repository for the "dangling blob" that is left behind because of this.
(This should probably be considered a bug in git, although it's not clear what to do with the staged but uncommitted version.)
git commit -a and/or paths: --only vs --include
Here I need to quote myself from just a moment ago. When using -a or paths, git commit starts by copying something—exactly what gets a bit complicated. If you look closely at the git commit documentation, you will find the -i or --include option (and a corresponding, but default, -o / --only option). These control what goes into the temporary index.
When using --include, git commit creates its temporary index from the current index. When using the default --only mode, git commit creates its temporary index from the HEAD commit.
(Because of the copy-back step at the end, the only way to see that both commands are in fact using a temporary index is to view the index in the middle of the commit operation. If we use --include and check after the commit is done, the regular index will match the new HEAD commit, as if git commit were adding to the regular index rather than to the temporary index. Fortunately it's very easy to view the regular index "in the middle", by not supplying the -m flag, so that git commit fires up the editor. While that's going on, run git status in another window, or using job control. Here's an example:
# At this point I've modified both a.py and mxgroup.py
# but have not `git add`ed either one.
$ git add a.py
$ git status --short
M a.py
M mxgroup.py
# We see that a plain "git commit" would commit a.py,
# but not mxgroup.py.
$ git commit -i mxgroup.py
# now waiting in the editor
# Now, in another window:
$ git status -s
M a.py
M mxgroup.py
This shows that the (regular) index is still set up the way we had it. Once we write the message out and exit the editor, the commit process will update the regular index for the new mxgroup.py entry, and the now-committed a.py change is also in the new HEAD commit, so git status will show neither file.)
Q2: Viewing logs
How can I see the remotes branch commits logs?(pushes) without doing git pull?
Git itself operates almost entirely locally. You may be able to do this directly with web viewers, but it's pretty convenient to just do locally, by first running git fetch.
The git pull command is actually meant as a convenience. There are two steps needed to incorporate other people's commits:
obtain those commits so that you have them locally; and
merge or rebase using those commits.
These two steps are handled by different git commands: git fetch does step 1, and git merge and git rebase—you must pick one of the two—does whichever version of step 2 you want.
The git pull command simply does step 1, then does step 2. It chooses merge unless you instruct it otherwise. For historical reasons, it has multiple ways of choosing which operation to run in step 2.
My recommendation is that as a newbie to git, you avoid git pull entirely. There are two reasons for this, only one of which is valid today (unless you're stuck with very old versions of git):
Traditionally, git pull has had various bugs, some of which can even lose work. (As far as I know these are all fixed since git 2.0.)
While it is convenient, git pull obscures what's happening and makes you choose merge-vs-rebase too early. It is true that rebase is almost always the right answer, but git pull defaults to doing merge, which means that its default is wrong for new users. Plus, of course, there's that "obscures what's happening" issue. If you knew about fetch and then rebase as separate steps, this question probably would not even have come up.
1The index is just a file, and you can find it in .git/index. You can make git use a different index by setting GIT_INDEX_FILE in the environment, but this is mainly meant for use by scripts like git stash.
2The cases I'm leaving out are:
Conflicted merges, which record up to three entries per path, using non-zero stage numbers. Once your resolve the conflict and git add the result, the nonzero stages are cleaned out and the normal stage-0 result is stored instead, and we're back to the normal, ready-to-commit case for that index intry.
Removing a file that is in the current commit (git rm, with or without --cached). This writes a special stage-0 entry marking the file as to-be-omitted, rather than simply removing the entry.
3If you're committing a merge, git allows the tree to match those of any or all parents, since the merge commit needs to record the multiple parents. The "empty" test is thus applied only to non-merge, single-parent commits. This is documented much better in modern git than it was in old versions of git, but it still has the wrong name.
4This has nothing to do with git itself. The idea here is to commit small, readable, understandable, and most importantly testable changes. Any time you find yourself writing up a commit as "do A and B, and fix C, and add D and E" it's an indication that you should probably split this into one commit per thing—in this case, about 5 separate commits.
[updated]
It is actually equivalent. When you commit a file directly, using git commit <filepath>, it stages the current file before committing. You do have to stage the file the first time the file is added before committing it (tell the repository to start tracking the file) using git add <file>.
Sample workflow:
git add <file>
Make some changes, yada yada
git commit -m "yada yada" <file>
Make some more changes, blah blah
git commit -m "blah blah" <file>
2.
To see the commit logs of a remote git repository, first use git fetch on the repository, then run git log <path/branch> to view the log.
See here: https://github.com/abhikp/git-test/wiki/View-the-commit-log-of-a-remote-branch

How to "attach" working dir to bare GIT repository

I have embedded Linux system that we want to store in Git. I have installed Git on the system, mount additional USB drive for storing Git data (bare repository). There is no problem with committing and pushing to the remote repository using commands like that:
cd /media/usb
git init --bare
git --work-tree=/ add -A
git --work-tree=/ commit
git --work-tree=/ push -u origin master
But when I clone bare repository to new USB drive and invoke git --work-tree=/ status I see all previously pushed files as deleted, and untracked. How to tell Git to use the work-tree?
The reason you are seeing previously committed files as deleted is that the git index (which is simply a file called index) in the first repository differs from the index in the second repository. The index in the first corresponds to the working tree, whereas the index in the second is uninitialized and therefore has no entries. The output from git status is the result of two comparisons:
between HEAD and the index (to determine staged changes to be committed)
between the index and the working tree (to determine unstaged changes which will not be committed)
In your case, HEAD in the second repository points to a commit which contains all the files you committed from your root filesystem, but the index is empty. So when git performs the first comparison, it thinks that each of these files has been staged for deletion on the next commit.
When git performs the second comparison, it finds that the working tree contains all the same files as the commit, but the index is of course still empty, so it sees these files as "new" untracked files. That is why you see all the files as both deleted and untracked.
The solution is very simple: initialize the second index so that it matches master:
git --work-tree=/ reset
While I'm here, I should point out some other issues with the commands you posted:
Firstly, your git add -U is adding all the git repository meta-data files to the repository. In other words, the repository is tracking itself. This is happening as a consequence of the way you use --work-tree, and is very bad. You should ensure that the repository files are ignored by adding them to info/exclude or .gitignore.
Secondly, you don't really want a bare repository here, just a detached working tree. You could have achieved this via git config core.bare false and export GIT_DIR=/media/usb; then you could run git commands from outside (i.e. above /media/usb), and you wouldn't have to continually include --work-tree=/ as a global option in each command.
Here's a complete test case which encapsulates everything I just covered except for the second bullet point:
#!/bin/sh
root=fakeroot
mkdir -p $root/media/usb{1,2} $root/{bin,etc}
echo a > $root/bin/sh
echo b > $root/etc/hosts
cd $root/media/usb1
git init --bare
# We don't want our git repository meta-data being tracked.
echo '/media/usb*/' >> info/exclude
git --work-tree=../.. add -A ../..
git --work-tree=../.. commit -m '1st commit'
echo c >> ../../etc/hosts
git --work-tree=../.. add -A ../..
git --work-tree=../.. commit -m '2nd commit'
git remote add origin ../usb2
git --git-dir=../usb2 init --bare
git push origin master
cd ../usb2
echo '/media/usb*/' >> info/exclude
echo "========================================="
echo "index in usb2 is not yet initialized:"
git --work-tree=../.. status
echo "========================================="
echo "initialize index to master (HEAD)"
git --work-tree=../.. reset
echo "========================================="
echo "now we have a clean working tree:"
git --work-tree=../.. status

Resources