Reputation: 5371
How to determine whether or not a given directory within a git repository contains unpushed, committed changes?
e.g. given a repository with two directories, A and B, return true if and only if the git log contains commits that updated the contents of A that have not been pushed to a remote master, yet.
For context, my use case is a hybrid repository with two language standards where I am interested in running a linter and test suite on the js directory pre-push, only if those files have been altered.
Upvotes: 0
Views: 184
Reputation: 488193
For context, my use case is a hybrid repository with two language standards where I am interested in running a linter and test suite on the js directory pre-push, only if those files have been altered.
In that case, you're really solving the wrong problem here. What you want to know is what commit(s) they (whoever they are) have, what commit(s) you're going to give them, and what test(s) you want to perform on those commit(s).
You get most of this directly in a pre-push
hook, where you should read all the lines of standard input. Each such line has four items on it, as described in the linked documentation. To make use of those lines, see the answer below.
Once you have the commit(s) in question, you can choose how to deal with them. For instance:
git rev-list local sha1 ^remote sha1
(e.g., git rev-list 1234567 ^fedcba9
)
will list all the commits you will be asking the other Git to add to the remote ref
(note that they may refuse). If you're not interested in all the intermediate commits, but rather only on the final result should the remote Git accept your update request, you might want to run git diff-tree --name-status -r $remotehash $localhash
, for instance, after setting variables localhash
and remotehash
to the corresponding SHA-1 values. (Note that git diff-tree
is a "plumbing" command, and hence does not alter its behavior based on individual user settings in ~/.gitconfig
. If you want to obey those settings, you can use git diff
directly, with its more familiar interfaces. Just be sure you know what you are doing.)
How to determine whether or not a given directory within a git repository contains unpunished, committed changes?
This question is ill-formed for several reasons. Let's look at this one first:
Consider:
$ git init
[initialization messages]
$ echo example > README
$ git add README
$ git commit -m first-commit
[commit output]
$ git remote add R1 ssh://example.com/path/to/repo1.git
$ git remote add R2 ssh://example.com/path/to/repo2.git
$ git push R1 master:master
[push output - assume it succeeds]
$ git push R2 master:master
[push output - assume it succeeds]
At this point, the local repository, branch master
, that we just created is in sync with both remotes, R1 and R2. (R1 and R2 represent --bare
repositories we must have already created on example.com
in the two paths.)
Now, let's do this:
$ echo different contents > README
$ git add README
$ cp -R /path/to/some/dir dir
$ git add dir
$ git commit -m second-commit
[commit output]
$ git push R1 master:master
[push output - assume it succeeds]
Our local repository is in sync with R1. It is not in sync with R2.
Before you can even ask about synchronization, you must pick some other Git repository. You can cheat a bit if there's only one other Git repository, the one named origin
.
Now consider what we've done with our three git push
commands above. The first two sent, to remote repositories R1 and R2 respectively:
README
file contents);master
branch to point to the final commit we just sent.The last git push
sent, to remote repository R1 only:
README
in it, plus all the files we copied into dir
);README
file contents and all the files in dir
);master
branch to point to the final commit we just sent (the current tip of our master
).Nowhere in all of this is there any mention of any one particular directory. A directory full of contents may come along for the ride, as in this case—we pushed a commit that has both README
and dir/*
—but that's because of the commits. The commit is a complete snapshot of the entire tree. A subsequent commit will have a new snapshot of every file, simply re-using existing files if they have not changed.
When you run git push
, you tell your Git: Call up another Git and get some branch names and hash IDs from that other Git. Use those to figure out which commits we must send them, based on my command line arguments. Send them all of those commits, along with any other required objects. Then, send them requests to update particular branch and/or other names they have, so that those branch and/or other names point to specific commits or other Git objects, some of which may be new ones we just sent them, and some of which may be existing commits that they already have.
The pre-push
hook gives you a chance to check things on your end, after getting their names-and-IDs, before sending them your update requests. There's no guarantee that they will accept your update requests, but if your pre-push hook exits with a non-zero status, your Git will tell their Git: Oops, sorry, never mind me! Bye! and never ask them to update anything after all.
The key to all of this is that everything works based off these hash IDs. Nothing depends on files or directories; everything depends on commits. Commits identify particular snapshots. It's your job to compare any two different snapshots, if you want to see what has changed between them.
Upvotes: -1
Reputation: 12176
You can use the below solution. Git will check against the remote to see if the commit is there are not. If a commit is found then you will get the commit, from that we are just getting the line count. If line count is 0 no commits are found
git log HEAD --pretty=oneline --not --remotes -- path | wc -l
Example
git log HEAD --pretty=oneline --not --remotes -- src/A | wc -l
Upvotes: 2